// A barebones gopher server written in Golang // This fork in particular uses flags instead of environment variables. // Copyright: // (C) 2021 Shokara Kou // (C) 2023 Izuru Yakumo package main import ( "bufio" "io" "log" "net" "os" "strings" "flag" ) var ( ADDR_IP string ADDR_PORT string HOSTNAME string SRVDIR string ) func formatLine(line string) string { trimmed := strings.TrimRight(line, "\r\n") splitted := strings.Split(trimmed, "\t") if len(splitted) == 3 { return line } else if len(splitted) == 2 { line += "\t" + HOSTNAME + "\t" + ADDR_PORT } else if len(splitted) == 1 { line += "\tErr\t" + HOSTNAME + "\t" + ADDR_PORT } return line + "\n" } func writeError(c net.Conn, msg string) { c.Write([]byte(formatLine("3" + msg))) } func printGophermap(c net.Conn, dir string) { file, err := os.Open(dir + "/Gophermap") if err != nil { writeError(c, err.Error()) log.Println(err) } defer func() { if err = file.Close(); err != nil { log.Println(err) } }() scanner := bufio.NewScanner(file) for scanner.Scan() { c.Write([]byte(formatLine(scanner.Text()) + "\n")) } c.Write([]byte(".\r\n")) } func printFile(c net.Conn, path string) { file, err := os.Open(path) if err != nil { writeError(c, err.Error()) log.Println(err) } defer func() { if err = file.Close(); err != nil { log.Println(err) } }() const bufSz = 1024 b := make([]byte, bufSz) for { readSz, err := file.Read(b) if err != nil { if err != io.EOF { log.Println(err) } break } c.Write(b[:readSz]) } } func connHandle(c net.Conn) { data, err := bufio.NewReader(c).ReadString('\n') if err != nil { log.Println(err) return } selector := strings.TrimRight(data, "\r\n") if selector == "" { printGophermap(c, "./") } else if strings.Contains(selector, "..") { writeError(c, "Selector contains ..") } else if selector[0] == '/' { info, err := os.Stat(selector[1:]) if err != nil { writeError(c, err.Error()) log.Println(err) c.Close() return } if info.IsDir() { printGophermap(c, selector[1:]) } else { printFile(c, selector[1:]) } } else { writeError(c, "Selector doesn't start with a /") } c.Close() } func init() { flag.StringVar(&ADDR_IP, "i", "0.0.0.0", "IP address for Tokiko to listen on .Default is 0.0.0.0") flag.StringVar(&ADDR_PORT, "p", "70", "Port for Tokiko to listen on. Default is 70") flag.StringVar(&HOSTNAME, "h", "localhost", "Hostname that gets printed for links. Default is localhost") flag.StringVar(&SRVDIR, "s", ".", "Root directory that gets served. Default is current directory") } func main() { flag.Parse() LISTEN_ADDR := ADDR_IP + ":" + ADDR_PORT log.Printf("Starting thomomys on %s\n", LISTEN_ADDR) os.Chdir(SRVDIR) l, err := net.Listen("tcp", LISTEN_ADDR) if err != nil { log.Fatal(err) return } defer l.Close() for { c, err := l.Accept() if err != nil { log.Println(err) return } go connHandle(c) } }