├── sendfile_other.go ├── sendfile_unix.go └── main.go /sendfile_other.go: -------------------------------------------------------------------------------- 1 | // +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris 2 | 3 | package main 4 | 5 | import ( 6 | "io" 7 | "net" 8 | "os" 9 | ) 10 | 11 | func sendfile(c *net.TCPConn, f *os.File, fi os.FileInfo) { 12 | io.Copy(c, f) 13 | } 14 | -------------------------------------------------------------------------------- /sendfile_unix.go: -------------------------------------------------------------------------------- 1 | // +build darwin dragonfly freebsd linux netbsd openbsd solaris 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "net" 8 | "os" 9 | "syscall" 10 | ) 11 | 12 | func sendfile(c *net.TCPConn, f *os.File, fi os.FileInfo) { 13 | sockFile, err := c.File() 14 | if err != nil { 15 | fmt.Fprint(c, Error(fmt.Sprintf("couldn't get file sock: %x", err))) 16 | } 17 | syscall.Sendfile(int(sockFile.Fd()), int(f.Fd()), nil, int(fi.Size())) 18 | } 19 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | var ( 16 | host = flag.String("host", "localhost", "hostname used in links") 17 | address = flag.String("address", "localhost", "listen on address") 18 | port = flag.String("port", "70", "listen on port") 19 | root string 20 | ) 21 | 22 | type Entry struct { 23 | Type byte 24 | Display string 25 | Selector string 26 | Hostname string 27 | Port string 28 | } 29 | 30 | func (e Entry) String() string { 31 | return fmt.Sprintf("%c%s\t%s\t%s\t%s\r\n", 32 | e.Type, e.Display, e.Selector, e.Hostname, e.Port) 33 | } 34 | 35 | type Listing []Entry 36 | 37 | func (l Listing) String() string { 38 | var b bytes.Buffer 39 | for _, e := range l { 40 | if e.Type == 0 { 41 | continue // skip sentinel value 42 | } 43 | fmt.Fprint(&b, e) 44 | } 45 | fmt.Fprint(&b, ".\r\n") 46 | return b.String() 47 | } 48 | 49 | func (l Listing) VisitDir(path string, f os.FileInfo) error { 50 | if len(l) == 0 { 51 | l = append(l, Entry{}) // sentinel value 52 | return nil 53 | } 54 | l = append(l, Entry{'1', f.Name(), path[len(root)-1:], *host, *port}) 55 | return filepath.SkipDir 56 | } 57 | 58 | var suffixes = map[string]byte{ 59 | "aiff": 's', 60 | "au": 's', 61 | "gif": 'g', 62 | "go": '0', 63 | "html": 'h', 64 | "jpeg": 'I', 65 | "jpg": 'I', 66 | "mp3": 's', 67 | "png": 'I', 68 | "txt": '0', 69 | "wav": 's', 70 | } 71 | 72 | func (l Listing) VisitFile(path string, f os.FileInfo) { 73 | t := byte('9') // Binary 74 | for s, c := range suffixes { 75 | if strings.HasSuffix(path, "."+s) { 76 | t = c 77 | break 78 | } 79 | } 80 | l = append(l, Entry{t, f.Name(), path[len(root)-1:], *host, *port}) 81 | } 82 | 83 | func Serve(c *net.TCPConn) { 84 | defer c.Close() 85 | connbuf := bufio.NewReader(c) 86 | p, _, err := connbuf.ReadLine() 87 | if err != nil { 88 | fmt.Fprint(c, Error("invalid request")) 89 | return 90 | } 91 | filename := root + filepath.Clean("/"+string(p)) 92 | fi, err := os.Stat(filename) 93 | if err != nil { 94 | fmt.Fprint(c, Error("not found")) 95 | return 96 | } 97 | if fi.IsDir() { 98 | var list Listing 99 | walkFn := func(path string, info os.FileInfo, err error) error { 100 | if info.IsDir() { 101 | return list.VisitDir(path, info) 102 | } 103 | 104 | list.VisitFile(path, info) 105 | return nil 106 | } 107 | 108 | filepath.Walk(filename, walkFn) 109 | fmt.Fprint(c, list) 110 | return 111 | } 112 | f, err := os.Open(filename) 113 | if err != nil { 114 | fmt.Fprint(c, Error("couldn't open file")) 115 | return 116 | } 117 | sendfile(c, f, fi) 118 | } 119 | 120 | func Error(msg string) Listing { 121 | return Listing{Entry{Type: 3, Display: msg}} 122 | } 123 | 124 | func main() { 125 | flag.Usage = func() { 126 | fmt.Fprintf(os.Stderr, "usage: %s directory\n", os.Args[0]) 127 | flag.PrintDefaults() 128 | os.Exit(2) 129 | } 130 | 131 | flag.Parse() 132 | if root = flag.Arg(0); root == "" { 133 | flag.Usage() 134 | } 135 | if strings.HasSuffix(root, "/") { 136 | root = root[:len(root)-1] 137 | } 138 | listenAddr := net.JoinHostPort(*address, *port) 139 | l, err := net.Listen("tcp", listenAddr) 140 | if err != nil { 141 | log.Fatal(err) 142 | } 143 | 144 | for { 145 | c, err := l.Accept() 146 | tcpConn := c.(*net.TCPConn) 147 | if err != nil { 148 | log.Fatal(err) 149 | } 150 | go Serve(tcpConn) 151 | } 152 | } 153 | --------------------------------------------------------------------------------