From 97570165616cd29c8aa4a9dfef82755ba1d3f450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Nu=C8=9Biu?= Date: Fri, 20 Oct 2017 18:08:25 +0300 Subject: [PATCH] Initial commit --- server/main.go | 27 ++++++++++++++ server/server/commands.go | 73 ++++++++++++++++++++++++++++++++++++ server/server/connection.go | 30 +++++++++++++++ server/server/errors.go | 19 ++++++++++ server/server/parser.go | 74 +++++++++++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 server/main.go create mode 100644 server/server/commands.go create mode 100644 server/server/connection.go create mode 100644 server/server/errors.go create mode 100644 server/server/parser.go diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..532d22a --- /dev/null +++ b/server/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "net" + "log" + "github.com/metonimie/simpleFTP/server/server" +) + +func main() { + listener, err := net.Listen("tcp", "localhost:8080") + if err != nil { + log.Fatal(err) + } + + log.Println("Hello world!") + log.Println("Running on:", "localhost", "port", "8080") + + for { + conn, err := listener.Accept() + if err != nil { + log.Print(err) + continue + } + + go server.HandleConnection(conn) + } +} diff --git a/server/server/commands.go b/server/server/commands.go new file mode 100644 index 0000000..889177d --- /dev/null +++ b/server/server/commands.go @@ -0,0 +1,73 @@ +package server + +import ( + "net" + "strings" + "os" + "log" + "io/ioutil" + "bytes" + "strconv" +) + +// PATH is the constant which should contain the fixed path where the simpleFTP server will run +// This will act like a root cage. +const PATH = "/Users/denis/GoglandProjects/golangBook/GoRoutines/" + +// SendFile sends the file to the client and returns true if it succeeds and false otherwise. +func SendFile(c net.Conn, path string) (int, error) { + var fileName string + + // Make sure the user can't request any files on the system. + lastForwardSlash := strings.LastIndex(path, "/") + if lastForwardSlash != -1 { + // Eliminate the last forward slash i.e ../../asdas will become asdas + fileName = path[lastForwardSlash + 1:] + } else { + fileName = path + } + + file, err := os.Open(PATH + fileName) + if err != nil { + // Open file failed. + log.Println(err) + return 0, err + } + defer file.Close() // Closing the fd when the function has exited. + + data, err := ioutil.ReadAll(file) + n, err := c.Write(data) + if err != nil { + log.Println(err) + return 0, err + } + // How is this even possible? + if n == 0 { + log.Println("0 bits written for:", path) + return 0, nil + } + + return n, nil +} + + +// ListFiles list the files from path and sends them to the connection +func ListFiles(c net.Conn) error { + files, err := ioutil.ReadDir(PATH) + if err != nil { + return err; + } + + buffer := bytes.NewBufferString("Directory Mode Size LastModified Name\n") + for _, f := range files { + buffer.WriteString(strconv.FormatBool(f.IsDir()) + " " + string(f.Mode().String()) + " " + + strconv.FormatInt(f.Size(), 10) + " " + f.ModTime().String() + " " + string(f.Name()) + " " + "\n") + } + + _, err = c.Write(buffer.Bytes()) + if err != nil { + return err + } + + return nil +} diff --git a/server/server/connection.go b/server/server/connection.go new file mode 100644 index 0000000..3d65e03 --- /dev/null +++ b/server/server/connection.go @@ -0,0 +1,30 @@ +package server + +import ( + "net" + "io" + "log" + "bufio" +) + +func HandleConnection(c net.Conn) { + defer c.Close() + io.WriteString(c, "Hello and welcome to simple ftp\n") + + log.Println(c.RemoteAddr(), "has connected.") + + // Process input + input := bufio.NewScanner(c) + for input.Scan() { + log.Println(c.RemoteAddr(), ":", input.Text()) + + err := ProcessInput(c, input.Text()) + if err != nil { + log.Println(err) + io.WriteString(c, err.Error()+"\n") + } + } + + // Client has left. + log.Println(c.RemoteAddr(), "has disconnected.") +} diff --git a/server/server/errors.go b/server/server/errors.go new file mode 100644 index 0000000..2e70b37 --- /dev/null +++ b/server/server/errors.go @@ -0,0 +1,19 @@ +package server + +import "errors" + +// InputError will be raised when the input is not right. +type InputError struct { + // The operation that caused the error. + Op string + // The error that occurred during the operation. + Err error +} + +func (e *InputError) Error() string { return "Error: " + e.Op + ": " + e.Err.Error() } + +var ( + InvalidCommand = errors.New("Invalid command.") + TooManyArguments = errors.New("Too many arguments.") + TooFewArguments = errors.New("Too few arguments.") +) diff --git a/server/server/parser.go b/server/server/parser.go new file mode 100644 index 0000000..e6e3438 --- /dev/null +++ b/server/server/parser.go @@ -0,0 +1,74 @@ +package server + +import ( + "net" + "strings" +) + +// checkArgumentsLength returns an error if length is not equal to expected. +func checkArgumentsLength(length int, expected int) error { + if length > expected { + return TooManyArguments + } else if length < expected { + return TooFewArguments + } + return nil +} + +func ProcessInput(c net.Conn, text string) error { + commands := strings.Fields(text) + commandsLen := len(commands) + + // Possibly empty input, just go on. + if commandsLen == 0 { + return nil + } + + switch commands[0] { + case "get": + // Check arguments + err := checkArgumentsLength(commandsLen, 2) + if err != nil { + return &InputError{commands[0], err} + } + + // Get the file + _, err = SendFile(c, commands[1]) + if err != nil { + return &InputError{"SendFile", err} + } + case "ls": + // Check arguments + err := checkArgumentsLength(commandsLen, 1) + if err != nil { + return &InputError{commands[0], err} + } + + err = ListFiles(c) + if err != nil { + return &InputError{commands[0], err} + } + case "clear": + // Check arguments + err := checkArgumentsLength(commandsLen, 1) + if err != nil { + return &InputError{commands[0], err} + } + + // Ansi clear: 1b 5b 48 1b 5b 4a + // clear | hexdump -C + var b []byte = []byte{0x1b, 0x5b, 0x48, 0x1b, 0x5b, 0x4a} + c.Write(b) + case "exit": + err := checkArgumentsLength(commandsLen, 1) + if err != nil { + return &InputError{commands[0], err} + } + + c.Close() + default: + return &InputError{commands[0], InvalidCommand} + } + + return nil +} \ No newline at end of file