diff --git a/README.md b/README.md index 9dd5f46..e090c8a 100644 --- a/README.md +++ b/README.md @@ -92,3 +92,5 @@ The following options can be set in `/etc/molly.conf`: * `LogPath`: Path to log file (default value `molly.log`). Note that all intermediate directories must exist, Molly Brown won't create them for you. +* `DirectorySort`: A string specifying how to sort files in automatically generated directory listings. Must be one of "Name", "Size" or "Time" (default value "Name"). +* `DirectoryReverse`: Boolean, if true automatically generated directory listings will list files in descending order of whatever `DirectorySort` is set to (default value false). diff --git a/config.go b/config.go index b8b773b..49f7dfe 100644 --- a/config.go +++ b/config.go @@ -1,23 +1,26 @@ package main import ( + "errors" "github.com/BurntSushi/toml" ) type Config struct { - Port int - Hostname string - CertPath string - KeyPath string - DocBase string - HomeDocBase string - GeminiExt string - DefaultLang string - LogPath string - TempRedirects map[string]string - PermRedirects map[string]string - CGIPaths []string - SCGIPaths map[string]string + Port int + Hostname string + CertPath string + KeyPath string + DocBase string + HomeDocBase string + GeminiExt string + DefaultLang string + LogPath string + TempRedirects map[string]string + PermRedirects map[string]string + CGIPaths []string + SCGIPaths map[string]string + DirectorySort string + DirectoryReverse bool } type MollyFile struct { @@ -43,6 +46,7 @@ func getConfig(filename string) (Config, error) { config.PermRedirects = make(map[string]string) config.CGIPaths = make([]string, 0) config.SCGIPaths = make(map[string]string) + config.DirectorySort = "Name" // Return defaults if no filename given if filename == "" { @@ -54,5 +58,13 @@ func getConfig(filename string) (Config, error) { if err != nil { return config, err } + + // Validate pseudo-enums + switch config.DirectorySort { + case "Name", "Size", "Time": + default: + return config, errors.New("Invalid DirectorySort value.") + } + return config, nil } diff --git a/handler.go b/handler.go index 4f91098..f0886ec 100644 --- a/handler.go +++ b/handler.go @@ -14,6 +14,7 @@ import ( "os" "path/filepath" "regexp" + "sort" "strings" "time" ) @@ -154,7 +155,7 @@ func handleGeminiRequest(conn net.Conn, config Config, logEntries chan LogEntry) } else { conn.Write([]byte("20 text/gemini\r\n")) log.Status = 20 - conn.Write([]byte(generateDirectoryListing(URL, path))) + conn.Write([]byte(generateDirectoryListing(URL, path, config))) } return } @@ -263,7 +264,7 @@ func parseMollyFiles(path string, info os.FileInfo, config *Config) { } -func generateDirectoryListing(URL *url.URL, path string) string { +func generateDirectoryListing(URL *url.URL, path string, config Config) string { var listing string files, err := ioutil.ReadDir(path) if err != nil { @@ -287,6 +288,21 @@ func generateDirectoryListing(URL *url.URL, path string) string { up := filepath.Dir(URL.Path) listing += fmt.Sprintf("=> %s %s\n", up, "..") } + // Sort files + sort.SliceStable(files, func(i, j int) bool { + if config.DirectoryReverse { + i, j = j, i + } + if config.DirectorySort == "Name" { + return files[i].Name() < files[j].Name() + } else if config.DirectorySort == "Size" { + return files[i].Size() < files[j].Size() + } else if config.DirectorySort == "Time" { + return files[i].ModTime().Before(files[j].ModTime()) + } + return false // Should not happen + }) + // Format lines for _, file := range files { // Skip dotfiles if strings.HasPrefix(file.Name(), ".") {