2019-04-10 20:20:50 +02:00
|
|
|
package ftp
|
|
|
|
|
|
|
|
import (
|
2020-03-10 11:41:46 +01:00
|
|
|
"bytes"
|
2022-03-10 01:57:17 +01:00
|
|
|
"crypto/tls"
|
2019-04-10 20:20:50 +02:00
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"net/textproto"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
2021-05-08 15:20:49 +02:00
|
|
|
"time"
|
2019-04-10 20:20:50 +02:00
|
|
|
)
|
|
|
|
|
2022-03-10 01:57:17 +01:00
|
|
|
const (
|
|
|
|
certPem = `-----BEGIN CERTIFICATE-----
|
|
|
|
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
|
|
|
|
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
|
|
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
|
|
|
|
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
|
|
|
|
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
|
|
|
|
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
|
|
|
|
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
|
|
|
|
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
|
|
|
|
6MF9+Yw1Yy0t
|
|
|
|
-----END CERTIFICATE-----`
|
|
|
|
keyPem = `-----BEGIN EC PRIVATE KEY-----
|
|
|
|
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
|
|
|
|
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
|
|
|
|
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
|
|
|
|
-----END EC PRIVATE KEY-----`
|
|
|
|
)
|
|
|
|
|
2019-04-10 20:20:50 +02:00
|
|
|
type ftpMock struct {
|
2022-03-10 01:57:17 +01:00
|
|
|
t *testing.T
|
|
|
|
address string
|
|
|
|
modtime string // no-time, std-time, vsftpd
|
|
|
|
listener net.Listener
|
|
|
|
proto *textproto.Conn
|
|
|
|
commands []string // list of received commands
|
|
|
|
lastFull string // full last command
|
|
|
|
rest int
|
|
|
|
fileCont *bytes.Buffer
|
|
|
|
dataConn *mockDataConn
|
|
|
|
tlsConfig *tls.Config
|
2019-04-10 20:20:50 +02:00
|
|
|
sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// newFtpMock returns a mock implementation of a FTP server
|
|
|
|
// For simplication, a mock instance only accepts a signle connection and terminates afer
|
|
|
|
func newFtpMock(t *testing.T, address string) (*ftpMock, error) {
|
2022-03-10 01:57:17 +01:00
|
|
|
return newFtpMockExt(t, address, false, "no-time")
|
2021-05-08 15:20:49 +02:00
|
|
|
}
|
|
|
|
|
2022-03-10 01:57:17 +01:00
|
|
|
func newFtpMockExt(t *testing.T, address string, ssl bool, modtime string) (*ftpMock, error) {
|
2021-05-08 15:20:49 +02:00
|
|
|
mock := &ftpMock{
|
2022-03-09 00:35:30 +01:00
|
|
|
t: t,
|
2021-05-08 15:20:49 +02:00
|
|
|
address: address,
|
|
|
|
modtime: modtime,
|
|
|
|
}
|
2019-04-10 20:20:50 +02:00
|
|
|
|
2022-03-10 01:57:17 +01:00
|
|
|
if ssl {
|
|
|
|
cert, err := tls.X509KeyPair([]byte(certPem), []byte(keyPem))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
|
|
l, err := tls.Listen("tcp", address+":0", mock.tlsConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.listener = l
|
|
|
|
} else {
|
|
|
|
l, err := net.Listen("tcp", address+":0")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mock.listener = l
|
2019-04-10 20:20:50 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-03-09 00:35:30 +01:00
|
|
|
go mock.listen()
|
2019-04-10 20:20:50 +02:00
|
|
|
|
|
|
|
return mock, nil
|
|
|
|
}
|
|
|
|
|
2022-03-09 00:35:30 +01:00
|
|
|
func (mock *ftpMock) listen() {
|
2019-04-10 20:20:50 +02:00
|
|
|
// Listen for an incoming connection.
|
|
|
|
conn, err := mock.listener.Accept()
|
|
|
|
if err != nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.t.Errorf("can not accept: %s", err)
|
2019-04-10 20:20:50 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do not accept incoming connections anymore
|
|
|
|
mock.listener.Close()
|
|
|
|
|
|
|
|
mock.Add(1)
|
|
|
|
defer mock.Done()
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
mock.proto = textproto.NewConn(conn)
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("220 FTP Server ready.")
|
2019-04-10 20:20:50 +02:00
|
|
|
|
|
|
|
for {
|
|
|
|
fullCommand, _ := mock.proto.ReadLine()
|
2021-02-13 18:42:15 +01:00
|
|
|
mock.lastFull = fullCommand
|
2019-04-10 20:20:50 +02:00
|
|
|
|
|
|
|
cmdParts := strings.Split(fullCommand, " ")
|
|
|
|
|
|
|
|
// Append to list of received commands
|
|
|
|
mock.commands = append(mock.commands, cmdParts[0])
|
|
|
|
|
|
|
|
// At least one command must have a multiline response
|
|
|
|
switch cmdParts[0] {
|
|
|
|
case "FEAT":
|
2021-05-08 15:20:49 +02:00
|
|
|
features := "211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n UTF8\r\n SIZE\r\n"
|
|
|
|
switch mock.modtime {
|
|
|
|
case "std-time":
|
|
|
|
features += " MDTM\r\n MFMT\r\n"
|
|
|
|
case "vsftpd":
|
|
|
|
features += " MDTM\r\n"
|
|
|
|
}
|
|
|
|
features += "211 End"
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine(features)
|
2019-04-10 20:20:50 +02:00
|
|
|
case "USER":
|
|
|
|
if cmdParts[1] == "anonymous" {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("331 Please send your password")
|
2019-04-10 20:20:50 +02:00
|
|
|
} else {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("530 This FTP server is anonymous only")
|
2019-04-10 20:20:50 +02:00
|
|
|
}
|
|
|
|
case "PASS":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "TYPE":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("200 Type set ok")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "CWD":
|
|
|
|
if cmdParts[1] == "missing-dir" {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("550 %s: No such file or directory", cmdParts[1])
|
2019-04-10 20:20:50 +02:00
|
|
|
} else {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("250 Directory successfully changed.")
|
2019-04-10 20:20:50 +02:00
|
|
|
}
|
|
|
|
case "DELE":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("250 File successfully removed.")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "MKD":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("257 Directory successfully created.")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "RMD":
|
|
|
|
if cmdParts[1] == "missing-dir" {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("550 No such file or directory")
|
2019-04-10 20:20:50 +02:00
|
|
|
} else {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("250 Directory successfully removed.")
|
2019-04-10 20:20:50 +02:00
|
|
|
}
|
|
|
|
case "PWD":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("257 \"/incoming\"")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "CDUP":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("250 CDUP command successful")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "SIZE":
|
|
|
|
if cmdParts[1] == "magic-file" {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("213 42")
|
2019-04-10 20:20:50 +02:00
|
|
|
} else {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("550 Could not get file size.")
|
2019-04-10 20:20:50 +02:00
|
|
|
}
|
|
|
|
case "PASV":
|
|
|
|
p, err := mock.listenDataConn()
|
|
|
|
if err != nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("451 %s.", err)
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
p1 := int(p / 256)
|
|
|
|
p2 := p % 256
|
|
|
|
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("227 Entering Passive Mode (127,0,0,1,%d,%d).", p1, p2)
|
2019-04-10 20:20:50 +02:00
|
|
|
case "EPSV":
|
|
|
|
p, err := mock.listenDataConn()
|
|
|
|
if err != nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("451 %s.", err)
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("229 Entering Extended Passive Mode (|||%d|)", p)
|
2019-04-10 20:20:50 +02:00
|
|
|
case "STOR":
|
|
|
|
if mock.dataConn == nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("425 Unable to build data connection: Connection refused")
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("150 please send")
|
2020-03-10 11:43:17 +01:00
|
|
|
mock.recvDataConn(false)
|
|
|
|
case "APPE":
|
|
|
|
if mock.dataConn == nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("425 Unable to build data connection: Connection refused")
|
2020-03-10 11:43:17 +01:00
|
|
|
break
|
|
|
|
}
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("150 please send")
|
2020-03-10 11:43:17 +01:00
|
|
|
mock.recvDataConn(true)
|
2019-04-10 20:20:50 +02:00
|
|
|
case "LIST":
|
|
|
|
if mock.dataConn == nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("425 Unable to build data connection: Connection refused")
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.dataConn.Wait()
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("150 Opening ASCII mode data connection for file list")
|
|
|
|
mock.dataConn.write([]byte("-rw-r--r-- 1 ftp wheel 0 Jan 29 10:29 lo"))
|
|
|
|
mock.printfLine("226 Transfer complete")
|
2019-04-10 20:20:50 +02:00
|
|
|
mock.closeDataConn()
|
|
|
|
case "NLST":
|
|
|
|
if mock.dataConn == nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("425 Unable to build data connection: Connection refused")
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.dataConn.Wait()
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("150 Opening ASCII mode data connection for file list")
|
|
|
|
mock.dataConn.write([]byte("/incoming"))
|
|
|
|
mock.printfLine("226 Transfer complete")
|
2019-04-10 20:20:50 +02:00
|
|
|
mock.closeDataConn()
|
|
|
|
case "RETR":
|
|
|
|
if mock.dataConn == nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("425 Unable to build data connection: Connection refused")
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.dataConn.Wait()
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("150 Opening ASCII mode data connection for file list")
|
|
|
|
mock.dataConn.write(mock.fileCont.Bytes()[mock.rest:])
|
2019-04-10 20:20:50 +02:00
|
|
|
mock.rest = 0
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("226 Transfer complete")
|
2019-04-10 20:20:50 +02:00
|
|
|
mock.closeDataConn()
|
|
|
|
case "RNFR":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("350 File or directory exists, ready for destination name")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "RNTO":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("250 Rename successful")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "REST":
|
|
|
|
if len(cmdParts) != 2 {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("500 wrong number of arguments")
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
rest, err := strconv.Atoi(cmdParts[1])
|
|
|
|
if err != nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("500 REST: %s", err)
|
2019-04-10 20:20:50 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
mock.rest = rest
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1])
|
2021-05-08 15:20:49 +02:00
|
|
|
case "MDTM":
|
|
|
|
var answer string
|
|
|
|
switch {
|
|
|
|
case mock.modtime == "no-time":
|
|
|
|
answer = "500 Unknown command MDTM"
|
|
|
|
case len(cmdParts) == 3 && mock.modtime == "vsftpd":
|
|
|
|
answer = "213 UTIME OK"
|
|
|
|
_, err := time.ParseInLocation(timeFormat, cmdParts[1], time.UTC)
|
|
|
|
if err != nil {
|
|
|
|
answer = "501 Can't get a time stamp"
|
|
|
|
}
|
|
|
|
case len(cmdParts) == 2:
|
|
|
|
answer = "213 20201213202400"
|
|
|
|
default:
|
|
|
|
answer = "500 wrong number of arguments"
|
|
|
|
}
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine(answer)
|
2021-05-08 15:20:49 +02:00
|
|
|
case "MFMT":
|
|
|
|
var answer string
|
|
|
|
switch {
|
|
|
|
case mock.modtime == "std-time" && len(cmdParts) == 3:
|
|
|
|
answer = "213 UTIME OK"
|
|
|
|
_, err := time.ParseInLocation(timeFormat, cmdParts[1], time.UTC)
|
|
|
|
if err != nil {
|
|
|
|
answer = "501 Can't get a time stamp"
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
answer = "500 Unknown command MFMT"
|
|
|
|
}
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine(answer)
|
2019-04-10 20:20:50 +02:00
|
|
|
case "NOOP":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("200 NOOP ok.")
|
2020-05-05 13:34:35 +02:00
|
|
|
case "OPTS":
|
|
|
|
if len(cmdParts) != 3 {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("500 wrong number of arguments")
|
2020-05-05 13:34:35 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
if (strings.Join(cmdParts[1:], " ")) == "UTF8 ON" {
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("200 OK, UTF-8 enabled")
|
2020-05-05 13:34:35 +02:00
|
|
|
}
|
2019-04-10 20:20:50 +02:00
|
|
|
case "REIN":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("220 Logged out")
|
2019-04-10 20:20:50 +02:00
|
|
|
case "QUIT":
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("221 Goodbye.")
|
2019-04-10 20:20:50 +02:00
|
|
|
return
|
2022-03-10 01:57:17 +01:00
|
|
|
case "PBSZ":
|
|
|
|
mock.printfLine("200 PBSZ ok.")
|
|
|
|
case "PROT":
|
|
|
|
mock.printfLine("200 PROT ok.")
|
2019-04-10 20:20:50 +02:00
|
|
|
default:
|
2022-03-09 00:35:30 +01:00
|
|
|
mock.printfLine("500 Unknown command %s.", cmdParts[0])
|
2019-04-10 20:20:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-09 00:35:30 +01:00
|
|
|
func (mock *ftpMock) printfLine(format string, args ...interface{}) {
|
|
|
|
if err := mock.proto.Writer.PrintfLine(format, args...); err != nil {
|
|
|
|
mock.t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mock *ftpMock) closeDataConn() {
|
2019-04-10 20:20:50 +02:00
|
|
|
if mock.dataConn != nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
if err := mock.dataConn.Close(); err != nil {
|
|
|
|
mock.t.Fatal(err)
|
|
|
|
}
|
2019-04-10 20:20:50 +02:00
|
|
|
mock.dataConn = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockDataConn struct {
|
2022-03-09 00:35:30 +01:00
|
|
|
t *testing.T
|
2022-03-10 01:57:17 +01:00
|
|
|
listener net.Listener
|
2019-04-10 20:20:50 +02:00
|
|
|
conn net.Conn
|
|
|
|
// WaitGroup is done when conn is accepted and stored
|
|
|
|
sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *mockDataConn) Close() (err error) {
|
|
|
|
if d.listener != nil {
|
|
|
|
err = d.listener.Close()
|
|
|
|
}
|
|
|
|
if d.conn != nil {
|
|
|
|
err = d.conn.Close()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-09 00:35:30 +01:00
|
|
|
func (d *mockDataConn) write(b []byte) {
|
|
|
|
if d.conn == nil {
|
|
|
|
d.t.Fatal("data conn is not opened")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := d.conn.Write(b); err != nil {
|
|
|
|
d.t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-10 20:20:50 +02:00
|
|
|
func (mock *ftpMock) listenDataConn() (int64, error) {
|
|
|
|
mock.closeDataConn()
|
|
|
|
|
|
|
|
l, err := net.Listen("tcp", mock.address+":0")
|
2022-03-10 01:57:17 +01:00
|
|
|
addr := l.Addr().String()
|
2019-04-10 20:20:50 +02:00
|
|
|
|
|
|
|
_, port, err := net.SplitHostPort(addr)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := strconv.ParseInt(port, 10, 32)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2022-03-09 00:35:30 +01:00
|
|
|
dataConn := &mockDataConn{
|
|
|
|
t: mock.t,
|
2022-03-10 01:57:17 +01:00
|
|
|
listener: l,
|
2022-03-09 00:35:30 +01:00
|
|
|
}
|
2019-04-10 20:20:50 +02:00
|
|
|
dataConn.Add(1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
// Listen for an incoming connection.
|
|
|
|
conn, err := dataConn.listener.Accept()
|
|
|
|
if err != nil {
|
2022-03-09 00:35:30 +01:00
|
|
|
// mock.t.Fatalf("can not accept data conn: %s", err)
|
2022-03-10 01:57:17 +01:00
|
|
|
dataConn.Done()
|
2019-04-10 20:20:50 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-10 01:57:17 +01:00
|
|
|
if mock.tlsConfig != nil {
|
|
|
|
tlsConn := tls.Server(conn, mock.tlsConfig)
|
|
|
|
if err := tlsConn.Handshake(); err != nil {
|
|
|
|
dataConn.Done()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
conn = tlsConn
|
|
|
|
}
|
|
|
|
|
2019-04-10 20:20:50 +02:00
|
|
|
dataConn.conn = conn
|
|
|
|
dataConn.Done()
|
|
|
|
}()
|
|
|
|
|
|
|
|
mock.dataConn = dataConn
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
2020-03-10 11:43:17 +01:00
|
|
|
func (mock *ftpMock) recvDataConn(append bool) {
|
2019-04-10 20:20:50 +02:00
|
|
|
mock.dataConn.Wait()
|
2020-03-10 11:43:17 +01:00
|
|
|
if !append {
|
|
|
|
mock.fileCont = new(bytes.Buffer)
|
|
|
|
}
|
2022-03-09 00:35:30 +01:00
|
|
|
|
|
|
|
if _, err := io.Copy(mock.fileCont, mock.dataConn.conn); err != nil {
|
|
|
|
mock.t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
mock.printfLine("226 Transfer Complete")
|
2019-04-10 20:20:50 +02:00
|
|
|
mock.closeDataConn()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mock *ftpMock) Addr() string {
|
|
|
|
return mock.listener.Addr().String()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Closes the listening socket
|
|
|
|
func (mock *ftpMock) Close() {
|
|
|
|
mock.listener.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper to return a client connected to a mock server
|
2019-04-10 20:25:16 +02:00
|
|
|
func openConn(t *testing.T, addr string, options ...DialOption) (*ftpMock, *ServerConn) {
|
2021-05-08 15:20:49 +02:00
|
|
|
return openConnExt(t, addr, "no-time", options...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func openConnExt(t *testing.T, addr, modtime string, options ...DialOption) (*ftpMock, *ServerConn) {
|
2022-03-10 01:57:17 +01:00
|
|
|
mock, err := newFtpMockExt(t, addr, false, modtime)
|
2019-04-10 20:20:50 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer mock.Close()
|
|
|
|
|
2019-04-23 14:03:05 +02:00
|
|
|
c, err := Dial(mock.Addr(), options...)
|
2019-04-10 20:20:50 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = c.Login("anonymous", "anonymous")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return mock, c
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper to close a client connected to a mock server
|
|
|
|
func closeConn(t *testing.T, mock *ftpMock, c *ServerConn, commands []string) {
|
2020-10-20 20:16:16 +02:00
|
|
|
expected := []string{"USER", "PASS", "FEAT", "TYPE", "OPTS"}
|
2019-04-10 20:20:50 +02:00
|
|
|
expected = append(expected, commands...)
|
|
|
|
expected = append(expected, "QUIT")
|
|
|
|
|
|
|
|
if err := c.Quit(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the connection to close
|
|
|
|
mock.Wait()
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(mock.commands, expected) {
|
|
|
|
t.Fatal("unexpected sequence of commands:", mock.commands, "expected:", expected)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConn4(t *testing.T) {
|
|
|
|
mock, c := openConn(t, "127.0.0.1")
|
|
|
|
closeConn(t, mock, c, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConn6(t *testing.T) {
|
|
|
|
mock, c := openConn(t, "[::1]")
|
|
|
|
closeConn(t, mock, c, nil)
|
|
|
|
}
|
2022-03-10 01:57:17 +01:00
|
|
|
|
|
|
|
func TestConnTLS(t *testing.T) {
|
|
|
|
mock, err := newFtpMockExt(t, "127.0.0.1", true, "std-time")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tlsConf := &tls.Config{
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
c, err := Dial(mock.Addr(), DialWithTLS(tlsConf), DialWithTimeout(1*time.Second))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = c.Login("anonymous", "anonymous")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = c.List(".")
|
|
|
|
if err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
closeConn(t, mock, c, []string{"PBSZ", "PROT", "EPSV", "LIST"})
|
|
|
|
}
|