package main import ( "fmt" "io" "net" "net/url" "os" "path/filepath" "sort" "strings" "github.com/h2non/filetype" ) func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath string) int { 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 { return writeStatus(c, statusTemporaryFailure) } // 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 }) writeSuccess(c, serve, geminiType, -1) fmt.Fprintf(c, "# %s%s", request.Path, newLine) 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(newLine + newLine)) if request.Path != "/" { c.Write([]byte("=> ../ ../" + newLine + newLine)) } 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 + newLine)) if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { c.Write([]byte(newLine)) continue } modified := "Never" if !info.ModTime().IsZero() { modified = info.ModTime().Format("2006-01-02 3:04 PM") } c.Write([]byte(modified + " - " + formatFileSize(info.Size()) + newLine + newLine)) } return statusSuccess } func serveFile(c net.Conn, serve *pathConfig, 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 var contentType string if strings.HasSuffix(filePath, ".html") && strings.HasSuffix(filePath, ".htm") { contentType = "text/html; charset=utf-8" } else if strings.HasSuffix(filePath, ".txt") && strings.HasSuffix(filePath, ".text") { contentType = "text/plain; charset=utf-8" } else if !strings.HasSuffix(filePath, ".gmi") && !strings.HasSuffix(filePath, ".gemini") { kind, _ := filetype.Match(buf[:n]) if kind != filetype.Unknown { contentType = kind.MIME.Value } } if contentType == "" { contentType = geminiType } size := int64(-1) info, err := file.Stat() if err == nil { size = info.Size() } writeSuccess(c, serve, contentType, size) // Write body c.Write(buf[:n]) io.Copy(c, file) }