package main import ( "fmt" "io" "net" "net/url" "os" "path/filepath" "sort" "strings" "github.com/h2non/filetype" ) func serveDirList(c net.Conn, request *url.URL, dirPath string) { var ( files []os.FileInfo numDirs int numFiles int ) err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } else if path == dirPath { return nil } files = append(files, info) if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { numDirs++ } else { numFiles++ } if info.IsDir() { return filepath.SkipDir } return nil }) if err != nil { writeStatus(c, statusTemporaryFailure) return } // List directories first sort.Slice(files, func(i, j int) bool { iDir := files[i].IsDir() || files[i].Mode()&os.ModeSymlink != 0 jDir := files[j].IsDir() || files[j].Mode()&os.ModeSymlink != 0 if iDir != jDir { return iDir } return i < j }) writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") fmt.Fprintf(c, "# %s\r\n", request.Path) if numDirs == 1 { c.Write([]byte("1 directory")) } else { fmt.Fprintf(c, "%d directories", numDirs) } c.Write([]byte(", ")) if numDirs == 1 { c.Write([]byte("1 file")) } else { fmt.Fprintf(c, "%d files", numFiles) } c.Write([]byte("\r\n\n")) if request.Path != "/" { c.Write([]byte("=> ../ ../\r\n\r\n")) } for _, info := range files { fileName := info.Name() filePath := url.PathEscape(info.Name()) if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { fileName += "/" filePath += "/" } c.Write([]byte("=> " + fileName + " " + filePath + "\r\n")) if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { c.Write([]byte("\r\n")) continue } modified := "Never" if !info.ModTime().IsZero() { modified = info.ModTime().Format("2006-01-02 3:04 PM") } c.Write([]byte(modified + " - " + formatFileSize(info.Size()) + "\r\n\r\n")) } } func serveFile(c net.Conn, filePath string) { // Open file file, _ := os.Open(filePath) defer file.Close() // Read file header buf := make([]byte, 261) n, _ := file.Read(buf) // Write response header size := int64(-1) info, err := file.Stat() if err == nil { size = info.Size() } var mimeType string if strings.HasSuffix(filePath, ".html") && strings.HasSuffix(filePath, ".htm") { mimeType = "text/html; charset=utf-8" } else if strings.HasSuffix(filePath, ".txt") && strings.HasSuffix(filePath, ".text") { mimeType = "text/plain; charset=utf-8" } else if !strings.HasSuffix(filePath, ".gmi") && !strings.HasSuffix(filePath, ".gemini") { kind, _ := filetype.Match(buf[:n]) if kind != filetype.Unknown { mimeType = kind.MIME.Value } } if mimeType == "" { mimeType = "text/gemini; charset=utf-8" } var meta string if !config.DisableSize && size >= 0 { meta = fmt.Sprintf("%s; size=%d", mimeType, size) } else { meta = mimeType } writeHeader(c, statusSuccess, meta) // Write body c.Write(buf[:n]) io.Copy(c, file) }