<commit_before>package ftp

import (
	"bytes"
	"errors"
	"io"
	"net"
	"net/textproto"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"testing"
)

type ftpMock struct {
	address  string
	listener *net.TCPListener
	proto    *textproto.Conn
	commands []string // list of received commands
	rest     int
	fileCont *bytes.Buffer
	dataConn *mockDataConn
	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) {
	var err error
	mock := &ftpMock{address: address}

	l, err := net.Listen("tcp", address+":0")
	if err != nil {
		return nil, err
	}

	tcpListener, ok := l.(*net.TCPListener)
	if !ok {
		return nil, errors.New("listener is not a net.TCPListener")
	}
	mock.listener = tcpListener

	go mock.listen(t)

	return mock, nil
}

func (mock *ftpMock) listen(t *testing.T) {
	// Listen for an incoming connection.
	conn, err := mock.listener.Accept()
	if err != nil {
		t.Errorf("can not accept: %s", err)
		return
	}

	// Do not accept incoming connections anymore
	mock.listener.Close()

	mock.Add(1)
	defer mock.Done()
	defer conn.Close()

	mock.proto = textproto.NewConn(conn)
	mock.proto.Writer.PrintfLine("220 FTP Server ready.")

	for {
		fullCommand, _ := mock.proto.ReadLine()

		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":
			mock.proto.Writer.PrintfLine("211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n SIZE\r\n211 End")
		case "USER":
			if cmdParts[1] == "anonymous" {
				mock.proto.Writer.PrintfLine("331 Please send your password")
			} else {
				mock.proto.Writer.PrintfLine("530 This FTP server is anonymous only")
			}
		case "PASS":
			mock.proto.Writer.PrintfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted")
		case "TYPE":
			mock.proto.Writer.PrintfLine("200 Type set ok")
		case "CWD":
			if cmdParts[1] == "missing-dir" {
				mock.proto.Writer.PrintfLine("550 %s: No such file or directory", cmdParts[1])
			} else {
				mock.proto.Writer.PrintfLine("250 Directory successfully changed.")
			}
		case "DELE":
			mock.proto.Writer.PrintfLine("250 File successfully removed.")
		case "MKD":
			mock.proto.Writer.PrintfLine("257 Directory successfully created.")
		case "RMD":
			if cmdParts[1] == "missing-dir" {
				mock.proto.Writer.PrintfLine("550 No such file or directory")
			} else {
				mock.proto.Writer.PrintfLine("250 Directory successfully removed.")
			}
		case "PWD":
			mock.proto.Writer.PrintfLine("257 \"/incoming\"")
		case "CDUP":
			mock.proto.Writer.PrintfLine("250 CDUP command successful")
		case "SIZE":
			if cmdParts[1] == "magic-file" {
				mock.proto.Writer.PrintfLine("213 42")
			} else {
				mock.proto.Writer.PrintfLine("550 Could not get file size.")
			}
		case "PASV":
			p, err := mock.listenDataConn()
			if err != nil {
				mock.proto.Writer.PrintfLine("451 %s.", err)
				break
			}

			p1 := int(p / 256)
			p2 := p % 256

			mock.proto.Writer.PrintfLine("227 Entering Passive Mode (127,0,0,1,%d,%d).", p1, p2)
		case "EPSV":
			p, err := mock.listenDataConn()
			if err != nil {
				mock.proto.Writer.PrintfLine("451 %s.", err)
				break
			}
			mock.proto.Writer.PrintfLine("229 Entering Extended Passive Mode (|||%d|)", p)
		case "STOR":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}
			mock.proto.Writer.PrintfLine("150 please send")
			mock.recvDataConn(false)
		case "APPE":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}
			mock.proto.Writer.PrintfLine("150 please send")
			mock.recvDataConn(true)
		case "LIST":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}

			mock.dataConn.Wait()
			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
			mock.dataConn.conn.Write([]byte("-rw-r--r--   1 ftp      wheel           0 Jan 29 10:29 lo"))
			mock.proto.Writer.PrintfLine("226 Transfer complete")
			mock.closeDataConn()
		case "NLST":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}

			mock.dataConn.Wait()
			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
			mock.dataConn.conn.Write([]byte("/incoming"))
			mock.proto.Writer.PrintfLine("226 Transfer complete")
			mock.closeDataConn()
		case "RETR":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}

			mock.dataConn.Wait()
			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
			mock.dataConn.conn.Write(mock.fileCont.Bytes()[mock.rest:])
			mock.rest = 0
			mock.proto.Writer.PrintfLine("226 Transfer complete")
			mock.closeDataConn()
		case "RNFR":
			mock.proto.Writer.PrintfLine("350 File or directory exists, ready for destination name")
		case "RNTO":
			mock.proto.Writer.PrintfLine("250 Rename successful")
		case "REST":
			if len(cmdParts) != 2 {
				mock.proto.Writer.PrintfLine("500 wrong number of arguments")
				break
			}
			rest, err := strconv.Atoi(cmdParts[1])
			if err != nil {
				mock.proto.Writer.PrintfLine("500 REST: %s", err)
				break
			}
			mock.rest = rest
			mock.proto.Writer.PrintfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1])
		case "NOOP":
			mock.proto.Writer.PrintfLine("200 NOOP ok.")
		case "REIN":
			mock.proto.Writer.PrintfLine("220 Logged out")<commit_msg>Udpate ftpMock<commit_after>package ftp

import (
	"bytes"
	"errors"
	"io"
	"net"
	"net/textproto"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"testing"
)

type ftpMock struct {
	address  string
	listener *net.TCPListener
	proto    *textproto.Conn
	commands []string // list of received commands
	rest     int
	fileCont *bytes.Buffer
	dataConn *mockDataConn
	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) {
	var err error
	mock := &ftpMock{address: address}

	l, err := net.Listen("tcp", address+":0")
	if err != nil {
		return nil, err
	}

	tcpListener, ok := l.(*net.TCPListener)
	if !ok {
		return nil, errors.New("listener is not a net.TCPListener")
	}
	mock.listener = tcpListener

	go mock.listen(t)

	return mock, nil
}

func (mock *ftpMock) listen(t *testing.T) {
	// Listen for an incoming connection.
	conn, err := mock.listener.Accept()
	if err != nil {
		t.Errorf("can not accept: %s", err)
		return
	}

	// Do not accept incoming connections anymore
	mock.listener.Close()

	mock.Add(1)
	defer mock.Done()
	defer conn.Close()

	mock.proto = textproto.NewConn(conn)
	mock.proto.Writer.PrintfLine("220 FTP Server ready.")

	for {
		fullCommand, _ := mock.proto.ReadLine()

		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":
			mock.proto.Writer.PrintfLine("211-Features:\r\n FEAT\r\n PASV\r\n EPSV\r\n UTF8\r\n SIZE\r\n211 End")
		case "USER":
			if cmdParts[1] == "anonymous" {
				mock.proto.Writer.PrintfLine("331 Please send your password")
			} else {
				mock.proto.Writer.PrintfLine("530 This FTP server is anonymous only")
			}
		case "PASS":
			mock.proto.Writer.PrintfLine("230-Hey,\r\nWelcome to my FTP\r\n230 Access granted")
		case "TYPE":
			mock.proto.Writer.PrintfLine("200 Type set ok")
		case "CWD":
			if cmdParts[1] == "missing-dir" {
				mock.proto.Writer.PrintfLine("550 %s: No such file or directory", cmdParts[1])
			} else {
				mock.proto.Writer.PrintfLine("250 Directory successfully changed.")
			}
		case "DELE":
			mock.proto.Writer.PrintfLine("250 File successfully removed.")
		case "MKD":
			mock.proto.Writer.PrintfLine("257 Directory successfully created.")
		case "RMD":
			if cmdParts[1] == "missing-dir" {
				mock.proto.Writer.PrintfLine("550 No such file or directory")
			} else {
				mock.proto.Writer.PrintfLine("250 Directory successfully removed.")
			}
		case "PWD":
			mock.proto.Writer.PrintfLine("257 \"/incoming\"")
		case "CDUP":
			mock.proto.Writer.PrintfLine("250 CDUP command successful")
		case "SIZE":
			if cmdParts[1] == "magic-file" {
				mock.proto.Writer.PrintfLine("213 42")
			} else {
				mock.proto.Writer.PrintfLine("550 Could not get file size.")
			}
		case "PASV":
			p, err := mock.listenDataConn()
			if err != nil {
				mock.proto.Writer.PrintfLine("451 %s.", err)
				break
			}

			p1 := int(p / 256)
			p2 := p % 256

			mock.proto.Writer.PrintfLine("227 Entering Passive Mode (127,0,0,1,%d,%d).", p1, p2)
		case "EPSV":
			p, err := mock.listenDataConn()
			if err != nil {
				mock.proto.Writer.PrintfLine("451 %s.", err)
				break
			}
			mock.proto.Writer.PrintfLine("229 Entering Extended Passive Mode (|||%d|)", p)
		case "STOR":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}
			mock.proto.Writer.PrintfLine("150 please send")
			mock.recvDataConn(false)
		case "APPE":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}
			mock.proto.Writer.PrintfLine("150 please send")
			mock.recvDataConn(true)
		case "LIST":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}

			mock.dataConn.Wait()
			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
			mock.dataConn.conn.Write([]byte("-rw-r--r--   1 ftp      wheel           0 Jan 29 10:29 lo"))
			mock.proto.Writer.PrintfLine("226 Transfer complete")
			mock.closeDataConn()
		case "NLST":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}

			mock.dataConn.Wait()
			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
			mock.dataConn.conn.Write([]byte("/incoming"))
			mock.proto.Writer.PrintfLine("226 Transfer complete")
			mock.closeDataConn()
		case "RETR":
			if mock.dataConn == nil {
				mock.proto.Writer.PrintfLine("425 Unable to build data connection: Connection refused")
				break
			}

			mock.dataConn.Wait()
			mock.proto.Writer.PrintfLine("150 Opening ASCII mode data connection for file list")
			mock.dataConn.conn.Write(mock.fileCont.Bytes()[mock.rest:])
			mock.rest = 0
			mock.proto.Writer.PrintfLine("226 Transfer complete")
			mock.closeDataConn()
		case "RNFR":
			mock.proto.Writer.PrintfLine("350 File or directory exists, ready for destination name")
		case "RNTO":
			mock.proto.Writer.PrintfLine("250 Rename successful")
		case "REST":
			if len(cmdParts) != 2 {
				mock.proto.Writer.PrintfLine("500 wrong number of arguments")
				break
			}
			rest, err := strconv.Atoi(cmdParts[1])
			if err != nil {
				mock.proto.Writer.PrintfLine("500 REST: %s", err)
				break
			}
			mock.rest = rest
			mock.proto.Writer.PrintfLine("350 Restarting at %s. Send STORE or RETRIEVE to initiate transfer", cmdParts[1])
		case "NOOP":
			mock.proto.Writer.PrintfLine("200 NOOP ok.")
		case "OPTS UTF8 ON":
			mock.proto.Writer.PrintfLine("200 OK, UTF-8 enabled")
		case "REIN":
			mock.proto.Writer.PrintfLine("220 Logged out")
{'fraction_non_alphanumeric': 0.13567330408369896, 'fraction_numerical': 0.028349645629429633, 'mean_word_length': 3.4931766489764975, 'pattern_counts': {'":': 49, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3248585', 'n_tokens_mistral': 4701, 'n_tokens_neox': 3971, 'n_words': 1035}