dirserv.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package dirserv
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "net/url"
  7. "path/filepath"
  8. "strings"
  9. "imuslab.com/arozos/mod/database"
  10. "imuslab.com/arozos/mod/fileservers"
  11. "imuslab.com/arozos/mod/filesystem/arozfs"
  12. "imuslab.com/arozos/mod/user"
  13. )
  14. /*
  15. dirserv.go
  16. This module help serve the virtual file system in apache like directory listing interface
  17. Suitable for legacy web browser
  18. */
  19. type Option struct {
  20. Sysdb *database.Database
  21. UserManager *user.UserHandler
  22. ServerPort int
  23. ServerUUID string
  24. }
  25. type Manager struct {
  26. enabled bool
  27. option *Option
  28. }
  29. //Create a new web directory server
  30. func NewDirectoryServer(option *Option) *Manager {
  31. //Create a table to store which user enabled dirlisting on their own root
  32. option.Sysdb.NewTable("dirserv")
  33. defaultEnable := false
  34. if option.Sysdb.KeyExists("dirserv", "enabled") {
  35. option.Sysdb.Read("dirserv", "enabled", &defaultEnable)
  36. }
  37. return &Manager{
  38. enabled: defaultEnable,
  39. option: option,
  40. }
  41. }
  42. func (m *Manager) DirServerEnabled() bool {
  43. return m.enabled
  44. }
  45. func (m *Manager) Toggle(enabled bool) error {
  46. m.enabled = enabled
  47. m.option.Sysdb.Write("dirserv", "enabled", m.enabled)
  48. return nil
  49. }
  50. func (m *Manager) ListEndpoints(userinfo *user.User) []*fileservers.Endpoint {
  51. results := []*fileservers.Endpoint{}
  52. results = append(results, &fileservers.Endpoint{
  53. ProtocolName: "//",
  54. Port: m.option.ServerPort,
  55. Subpath: "/fileview",
  56. })
  57. return results
  58. }
  59. /*
  60. Router request handler
  61. */
  62. func (m *Manager) ServerWebFileRequest(w http.ResponseWriter, r *http.Request) {
  63. if !m.enabled {
  64. //Dirlisting is not enabled.
  65. http.NotFound(w, r)
  66. return
  67. }
  68. //Request basic auth
  69. username, password, ok := r.BasicAuth()
  70. if !ok {
  71. w.Header().Set("WWW-Authenticate", `Basic realm="`+m.option.ServerUUID+`", charset="UTF-8"`)
  72. http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
  73. return
  74. }
  75. //Validate username and password
  76. allowAccess, reason := m.option.UserManager.GetAuthAgent().ValidateUsernameAndPasswordWithReason(username, password)
  77. if !allowAccess {
  78. w.Header().Set("WWW-Authenticate", `Basic realm="`+m.option.ServerUUID+`", charset="UTF-8"`)
  79. http.Error(w, "401 - Unauthorized: "+reason, http.StatusUnauthorized)
  80. return
  81. }
  82. //Get user info
  83. userinfo, err := m.option.UserManager.GetUserInfoFromUsername(username)
  84. if err != nil {
  85. http.Error(w, "500 - Internal Server Error: "+err.Error(), http.StatusInternalServerError)
  86. return
  87. }
  88. requestPath := arozfs.ToSlash(filepath.Clean(r.RequestURI))
  89. requestPath = requestPath[1:] //Trim away the first "/"
  90. pathChunks := strings.Split(requestPath, "/")[1:] //Trim away the fileview prefix
  91. html := ""
  92. if len(pathChunks) == 0 {
  93. //Show root
  94. html += getPageHeader("/")
  95. fshs := userinfo.GetAllFileSystemHandler()
  96. for _, fsh := range fshs {
  97. html += getItemHTML(fsh.Name, arozfs.ToSlash(filepath.Join(r.RequestURI, fsh.UUID)), true, "-", "-")
  98. }
  99. } else {
  100. //Show path inside fsh
  101. fshId := pathChunks[0]
  102. subpath := strings.Join(pathChunks[1:], "/")
  103. targetFsh, err := userinfo.GetFileSystemHandlerFromVirtualPath(fshId + ":/")
  104. if err != nil {
  105. http.Error(w, "404 - Not Found: "+err.Error(), http.StatusNotFound)
  106. return
  107. }
  108. sp, err := url.QueryUnescape(subpath)
  109. if err != nil {
  110. sp = subpath
  111. }
  112. subpath = sp
  113. html += getPageHeader(fshId + ":/" + subpath)
  114. fshAbs := targetFsh.FileSystemAbstraction
  115. rpath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  116. if err != nil {
  117. http.Error(w, "500 - Virtual Path Conversion Failed: "+err.Error(), http.StatusNotFound)
  118. return
  119. }
  120. if fshAbs.IsDir(rpath) {
  121. //Append a back button
  122. html += getBackButton(r.RequestURI)
  123. //Load Directory
  124. entries, err := fshAbs.ReadDir(rpath)
  125. if err != nil {
  126. http.Error(w, "500 - Internal Server Error: "+err.Error(), http.StatusInternalServerError)
  127. return
  128. }
  129. for _, entry := range entries {
  130. finfo, err := entry.Info()
  131. if err != nil {
  132. continue
  133. }
  134. html += getItemHTML(entry.Name(), arozfs.ToSlash(filepath.Join(r.RequestURI, entry.Name())), entry.IsDir(), finfo.ModTime().Format("2006-01-02 15:04:05"), byteCountIEC(finfo.Size()))
  135. }
  136. } else {
  137. //Serve the file
  138. f, err := fshAbs.ReadStream(rpath)
  139. if err != nil {
  140. fmt.Println(err)
  141. http.Error(w, "500 - Internal Server Error: "+err.Error(), http.StatusInternalServerError)
  142. return
  143. }
  144. defer f.Close()
  145. io.Copy(w, f)
  146. }
  147. }
  148. html += getPageFooter()
  149. w.Write([]byte(html))
  150. }