diff --git a/book_with_bridge/ftp_server/ftp/conn.go b/book_with_bridge/ftp_server/ftp/conn.go new file mode 100644 index 0000000..6947fea --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/conn.go @@ -0,0 +1,62 @@ +package ftp + +import ( + "bufio" + "fmt" + "net" + "strings" +) + +type Conn struct { + conn net.Conn + dataType dataType + dataPort *dataPort + rootDir string + workDir string +} + +func NewConn(conn net.Conn, rootDir string) *Conn { + return &Conn{ + conn: conn, + rootDir: rootDir, + workDir: "/", + } +} + +func Serve (c *Conn) { + c.respond(status220) + + s := bufio.NewScanner(c.conn) + for s.Scan() { + input := strings.Fields(s.Text()) + if len(input) == 0 { + continue + } + command, args := input[0], input[1:] + fmt.Printf("<< %s %v", command, args) + + switch command { + case "CWD": // cd + c.cwd(args) + case "LIST": // ls + c.list(args) + case "PORT": + c.port(args) + case "USER": + c.user(args) + case "QUIT": // close + c.respond(status221) + return + case "RETR": // get + c.retr(args) + case "TYPE": + c.setDataType(args) + default: + c.respond(status502) + } + } + if s.Err() != nil { + fmt.Print(s.Err()) + } + +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/ftp/cwd.go b/book_with_bridge/ftp_server/ftp/cwd.go new file mode 100644 index 0000000..c66093a --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/cwd.go @@ -0,0 +1,25 @@ +package ftp + +import ( + "fmt" + "os" + "path/filepath" +) + +func (c *Conn) cwd(args []string) { + if len(args) != 1 { + c.respond(status501) + return + } + + workDir := filepath.Join(c.workDir, args[0]) + absPath := filepath.Join(c.rootDir, workDir) + _, err := os.Stat(absPath) + if err != nil { + fmt.Print(err) + c.respond(status550) + return + } + c.workDir = workDir + c.respond(status200) +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/ftp/dataconn.go b/book_with_bridge/ftp_server/ftp/dataconn.go new file mode 100644 index 0000000..8cd781e --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/dataconn.go @@ -0,0 +1,65 @@ +package ftp + +import "net" + +func (c *Conn) dataConnect() (net.Conn, error) { + conn, err := net.Dial("tcp", c.dataPort.toAddress()) + if err != nil { + return nil, err + } + return conn, nil +} + + dataConn, err := c.dataConnect() + if err != nil { + log.Print(err) + c.respond(status425) + return + } + defer dataConn.Close() + + for _, file := range files { + _, err := fmt.Fprint(dataConn, file.Name(), c.EOL()) + if err != nil { + log.Print(err) + c.respond(status426) + } + } + _, err = fmt.Fprintf(dataConn, c.EOL()) + if err != nil { + log.Print(err) + c.respond(status426) + } + + c.respond(status226) + + func (c *Conn) retr(args []string) { + if len(args) != 1 { + c.respond(status501) + return + } + + path := filepath.Join(c.rootDir, c.workDir, args[0]) + file, err := os.Open(path) + if err != nil { + log.Print(err) + c.respond(status550) + } + c.respond(status150) + + dataConn, err := c.dataConnect() + if err != nil { + log.Print(err) + c.respond(status425) + } + defer dataConn.Close() + + _, err = io.Copy(dataConn, file) + if err != nil { + log.Print(err) + c.respond(status426) + return + } + io.WriteString(dataConn, c.EOL()) + c.respond(status226) +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/ftp/datatype.go b/book_with_bridge/ftp_server/ftp/datatype.go new file mode 100644 index 0000000..a88ffa7 --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/datatype.go @@ -0,0 +1,25 @@ +package ftp + +type dataType int + +const ( + ascii dataType = iota + binary +) + +func (c *Conn) setDataType(args []string) { + if len(args) == 0 { + c.respond(status501) + } + + switch args[0] { + case "A": + c.dataType = ascii + case "I": // image/binary + c.dataType = binary + default: + c.respond(status504) + return + } + c.respond(status200) +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/ftp/list.go b/book_with_bridge/ftp_server/ftp/list.go new file mode 100644 index 0000000..072b7a8 --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/list.go @@ -0,0 +1,47 @@ +package ftp + +import ( + "fmt" + "io/ioutil" + "path/filepath" +) + +func (c *Conn) list(args []string) { + var target string + if len(args) > 0 { + target = filepath.Join(c.rootDir, c.workDir, args[0]) + } else { + target = filepath.Join(c.rootDir, c.workDir) + } + + files, err := ioutil.ReadDir(target) + if err != nil { + fmt.Print(err) + c.respond(status550) + return + } + c.respond(status150) + + dataConn, err := c.dataConnect() + if err != nil { + fmt.Print(err) + c.respond(status425) + return + } + defer dataConn.Close() + + for _, file := range files { + _, err := fmt.Fprint(dataConn, file.Name(), c.EOL()) + if err != nil { + fmt.Print(err) + c.respond(status426) + } + } + _, err = fmt.Fprintf(dataConn, c.EOL()) + if err != nil { + fmt.Print(err) + c.respond(status426) + } + + c.respond(status226) +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/ftp/port.go b/book_with_bridge/ftp_server/ftp/port.go new file mode 100644 index 0000000..9dc291c --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/port.go @@ -0,0 +1,42 @@ +package ftp + +import "fmt" + +func (c *Conn) port(args []string) { + if len(args) != 1 { + c.respond(status501) + return + } + dataPort, err := dataPortFromHostPort(args[0]) + if err != nil { + fmt.Print(err) + c.respond(status501) + return + } + c.dataPort = dataPort + c.respond(status200) +} + +type dataPort struct { + h1, h2, h3, h4 int // host + p1, p2 int // port +} + +func dataPortFromHostPort(hostPort string) (*dataPort, error) { + var dp dataPort + _, err := fmt.Sscanf(hostPort, "%d,%d,%d,%d,%d,%d", + &dp.h1, &dp.h2, &dp.h3, &dp.h4, &dp.p1, &dp.p2) + if err != nil { + return nil, err + } + return &dp, nil +} + +func (d *dataPort) toAddress() string { + if d == nil { + return "" + } + // convert hex port bytes to decimal port + port := d.p1<<8 + d.p2 + return fmt.Sprintf("%d.%d.%d.%d:%d", d.h1, d.h2, d.h3, d.h4, port) +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/ftp/respond.go b/book_with_bridge/ftp_server/ftp/respond.go new file mode 100644 index 0000000..3b4f72a --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/respond.go @@ -0,0 +1,40 @@ +package ftp + +import "fmt" + +const ( + status150 = "150 File status okay; about to open data connection." + status200 = "200 Command okay." + status220 = "220 Service ready for new user." + status221 = "221 Service closing control connection." + status226 = "226 Closing data connection. Requested file action successful." + status230 = "230 User %s logged in, proceed." + status425 = "425 Can't open data connection." + status426 = "426 Connection closed; transfer aborted." + status501 = "501 Syntax error in parameters or arguments." + status502 = "502 Command not implemented." + status504 = "504 Cammand not implemented for that parameter." + status550 = "550 Requested action not taken. File unavailable." +) + +// respond copies a string to the client and terminates it with the appropriate FTP line terminator +// for the datatype. +func (c *Conn) respond(s string) { + fmt.Print(">> ", s) + _, err := fmt.Fprint(c.conn, s, c.EOL()) + if err != nil { + fmt.Print(err) + } +} + +// EOL returns the line terminator matching the FTP standard for the datatype. +func (c *Conn) EOL() string { + switch c.dataType { + case ascii: + return "\r\n" + case binary: + return "\n" + default: + return "\n" + } +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/ftp/user.go b/book_with_bridge/ftp_server/ftp/user.go new file mode 100644 index 0000000..861c8f6 --- /dev/null +++ b/book_with_bridge/ftp_server/ftp/user.go @@ -0,0 +1,10 @@ +package ftp + +import ( + "fmt" + "strings" +) + +func (c *Conn) user(args []string) { + c.respond(fmt.Sprintf(status230, strings.Join(args, " "))) +} \ No newline at end of file diff --git a/book_with_bridge/ftp_server/go.mod b/book_with_bridge/ftp_server/go.mod new file mode 100644 index 0000000..1da823a --- /dev/null +++ b/book_with_bridge/ftp_server/go.mod @@ -0,0 +1,3 @@ +module main + +go 1.24.5 diff --git a/book_with_bridge/ftp_server/main.go b/book_with_bridge/ftp_server/main.go new file mode 100644 index 0000000..cd7ffd4 --- /dev/null +++ b/book_with_bridge/ftp_server/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net" + "path/filepath" + "main/ftp" +) + +var port int +var rootDir string + +func init() { + flag.IntVar(&port,"port",9090,"port to listet") + flag.StringVar(&rootDir,"rootDir","public","root Directory") + flag.Parse() +} + +func handleConn(conn net.Conn) { + defer conn.Close() + + absPath, err := filepath.Abs(rootDir) + if err != nil { + fmt.Print(err) + } + ftp.Serve(ftp.NewConn(conn,absPath)) +} + +func main() { + server := fmt.Sprintf(":%d",port) + listener,err := net.Listen("tcp",server) + if err != nil { + log.Fatal(err) + } + for { + conn, err := listener.Accept() + if err != nil { + fmt.Print(err) + continue + } + handleConn(conn) + } +}