dirhandler.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package engine
  2. // Directory Index
  3. import (
  4. "bytes"
  5. "net/http"
  6. "path/filepath"
  7. "strings"
  8. "github.com/go-gcfg/gcfg"
  9. log "github.com/sirupsen/logrus"
  10. "github.com/xyproto/algernon/themes"
  11. "github.com/xyproto/algernon/utils"
  12. )
  13. // List of filenames that should be displayed instead of a directory listing
  14. var indexFilenames = []string{"index.lua", "index.html", "index.md", "index.txt", "index.pongo2", "index.tmpl", "index.po2", "index.amber", "index.happ", "index.hyper", "index.hyper.js", "index.hyper.jsx", "index.tl"}
  15. const (
  16. dotSlash = "." + utils.Pathsep /* ./ */
  17. doubleP = utils.Pathsep + utils.Pathsep /* // */
  18. dirconfFilename = ".algernon"
  19. )
  20. // DirConfig keeps a directory listing configuration
  21. type DirConfig struct {
  22. Main struct {
  23. Title string
  24. Theme string
  25. }
  26. }
  27. // DirectoryListing serves the given directory as a web page with links the the contents
  28. func (ac *Config) DirectoryListing(w http.ResponseWriter, req *http.Request, rootdir, dirname, theme string) {
  29. var (
  30. buf bytes.Buffer
  31. fullFilename string
  32. URLpath string
  33. title = dirname
  34. )
  35. // Remove a trailing slash after the root directory, if present
  36. rootdir = strings.TrimSuffix(rootdir, "/")
  37. // Fill the coming HTML body with a list of all the filenames in `dirname`
  38. for _, filename := range utils.GetFilenames(dirname) {
  39. if filename == dirconfFilename {
  40. // Skip
  41. continue
  42. }
  43. // Find the full name
  44. fullFilename = dirname
  45. // Add a "/" after the directory name, if missing
  46. if !strings.HasSuffix(fullFilename, utils.Pathsep) {
  47. fullFilename += utils.Pathsep
  48. }
  49. // Add the filename at the end
  50. fullFilename += filename
  51. // Remove the root directory from the link path
  52. URLpath = fullFilename[len(rootdir)+1:]
  53. // Output different entries for files and directories
  54. buf.WriteString(themes.HTMLLink(filename, URLpath, ac.fs.IsDir(fullFilename)))
  55. }
  56. // Read directory configuration, if present
  57. fullDirConfFilename := filepath.Join(dirname, dirconfFilename)
  58. if ac.fs.Exists(fullDirConfFilename) {
  59. var dirConf DirConfig
  60. if err := gcfg.ReadFileInto(&dirConf, fullDirConfFilename); err == nil { // if no error
  61. if dirConf.Main.Title != "" {
  62. title = dirConf.Main.Title
  63. }
  64. if dirConf.Main.Theme != "" {
  65. theme = dirConf.Main.Theme
  66. }
  67. }
  68. } else {
  69. // Strip the leading "./" from the current directory
  70. title = strings.TrimPrefix(title, dotSlash)
  71. // Replace "//" with just "/"
  72. title = strings.ReplaceAll(title, doubleP, utils.Pathsep)
  73. }
  74. // Check if the current page contents are empty
  75. if buf.Len() == 0 {
  76. buf.WriteString("Empty directory")
  77. }
  78. htmldata := themes.MessagePageBytes(title, buf.Bytes(), theme)
  79. // If the auto-refresh feature has been enabled
  80. if ac.autoRefresh {
  81. // Insert JavaScript for refreshing the page into the generated HTML
  82. htmldata = ac.InsertAutoRefresh(req, htmldata)
  83. }
  84. // Serve the page
  85. w.Header().Add("Content-Type", "text/html;charset=utf-8")
  86. ac.DataToClient(w, req, dirname, htmldata)
  87. }
  88. // DirPage serves a directory, using index.* files, if present.
  89. // The directory must exist.
  90. // rootdir is the base directory (can be ".")
  91. // dirname is the specific directory that is to be served (should never be ".")
  92. func (ac *Config) DirPage(w http.ResponseWriter, req *http.Request, rootdir, dirname, theme string) {
  93. // Check if we are instructed to quit after serving the first file
  94. if ac.quitAfterFirstRequest {
  95. go ac.quitSoon("Quit after first request", defaultSoonDuration)
  96. }
  97. // If the URL does not end with a slash, redirect to an URL that does
  98. if !strings.HasSuffix(req.URL.Path, "/") {
  99. if req.Method == "POST" {
  100. log.Warn("Redirecting a POST request: " + req.URL.Path + " -> " + req.URL.Path + "/.")
  101. log.Warn("Header data may be lost! Please add the missing slash.")
  102. }
  103. http.Redirect(w, req, req.URL.Path+"/", http.StatusMovedPermanently)
  104. return
  105. }
  106. // Handle the serving of index files, if needed
  107. var filename string
  108. for _, indexfile := range indexFilenames {
  109. filename = filepath.Join(dirname, indexfile)
  110. if ac.fs.Exists(filename) {
  111. ac.FilePage(w, req, filename, ac.defaultLuaDataFilename)
  112. return
  113. }
  114. }
  115. // Serve handler.lua, if found in ancestors
  116. var ancestor string
  117. ancestor = filepath.Dir(dirname)
  118. for x := 0; x < 100; x++ { // a maximum of 100 directories deep
  119. filename = filepath.Join(ancestor, "handler.lua")
  120. if ac.fs.Exists(filename) {
  121. ac.FilePage(w, req, filename, ac.defaultLuaDataFilename)
  122. return
  123. }
  124. if ancestor == "." {
  125. break
  126. }
  127. ancestor = filepath.Dir(ancestor)
  128. }
  129. // Serve a directory listing if no index file is found
  130. ac.DirectoryListing(w, req, rootdir, dirname, theme)
  131. }