mediaServer.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. package main
  2. import (
  3. "errors"
  4. "io"
  5. "log"
  6. "net/http"
  7. "net/url"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "imuslab.com/arozos/mod/common"
  12. "imuslab.com/arozos/mod/filesystem"
  13. fs "imuslab.com/arozos/mod/filesystem"
  14. "imuslab.com/arozos/mod/network/gzipmiddleware"
  15. )
  16. /*
  17. Media Server
  18. This function serve large file objects like video and audio file via asynchronize go routine :)
  19. Example usage:
  20. /media/?file=user:/Desktop/test/02.Orchestra- エミール (Addendum version).mp3
  21. /media/?file=user:/Desktop/test/02.Orchestra- エミール (Addendum version).mp3&download=true
  22. This will serve / download the file located at files/users/{username}/Desktop/test/02.Orchestra- エミール (Addendum version).mp3
  23. PLEASE ALWAYS USE URLENCODE IN THE LINK PASSED INTO THE /media ENDPOINT
  24. */
  25. func mediaServer_init() {
  26. if *enable_gzip {
  27. http.HandleFunc("/media/", gzipmiddleware.CompressFunc(serverMedia))
  28. http.HandleFunc("/media/getMime/", gzipmiddleware.CompressFunc(serveMediaMime))
  29. } else {
  30. http.HandleFunc("/media/", serverMedia)
  31. http.HandleFunc("/media/getMime/", serveMediaMime)
  32. }
  33. //Download API always bypass gzip no matter if gzip mode is enabled
  34. http.HandleFunc("/media/download/", serverMedia)
  35. }
  36. //This function validate the incoming media request and return the real path for the targed file
  37. func media_server_validateSourceFile(w http.ResponseWriter, r *http.Request) (*filesystem.FileSystemHandler, string, error) {
  38. username, err := authAgent.GetUserName(w, r)
  39. if err != nil {
  40. return nil, "", errors.New("User not logged in")
  41. }
  42. userinfo, _ := userHandler.GetUserInfoFromUsername(username)
  43. //Validate url valid
  44. if strings.Count(r.URL.String(), "?") > 1 {
  45. return nil, "", errors.New("Invalid paramters. Multiple ? found")
  46. }
  47. targetfile, _ := common.Mv(r, "file", false)
  48. targetfile, err = url.QueryUnescape(targetfile)
  49. if err != nil {
  50. return nil, "", err
  51. }
  52. if targetfile == "" {
  53. return nil, "", errors.New("Missing paramter 'file'")
  54. }
  55. //Translate the virtual directory to realpath
  56. fsh, subpath, err := GetFSHandlerSubpathFromVpath(targetfile)
  57. if err != nil {
  58. return nil, "", errors.New("Unable to load from target file system")
  59. }
  60. fshAbs := fsh.FileSystemAbstraction
  61. realFilepath, err := fshAbs.VirtualPathToRealPath(subpath, userinfo.Username)
  62. if fshAbs.FileExists(realFilepath) && fshAbs.IsDir(realFilepath) {
  63. return nil, "", errors.New("Given path is not a file")
  64. }
  65. if err != nil {
  66. return nil, "", errors.New("Unable to translate the given filepath")
  67. }
  68. if !fshAbs.FileExists(realFilepath) {
  69. //Sometime if url is not URL encoded, this error might be shown as well
  70. //Try to use manual segmentation
  71. originalURL := r.URL.String()
  72. //Must be pre-processed with system special URI Decode function to handle edge cases
  73. originalURL = fs.DecodeURI(originalURL)
  74. if strings.Contains(originalURL, "&download=true") {
  75. originalURL = strings.ReplaceAll(originalURL, "&download=true", "")
  76. } else if strings.Contains(originalURL, "download=true") {
  77. originalURL = strings.ReplaceAll(originalURL, "download=true", "")
  78. }
  79. if strings.Contains(originalURL, "&file=") {
  80. originalURL = strings.ReplaceAll(originalURL, "&file=", "file=")
  81. }
  82. urlInfo := strings.Split(originalURL, "file=")
  83. possibleVirtualFilePath := urlInfo[len(urlInfo)-1]
  84. possibleRealpath, err := fshAbs.VirtualPathToRealPath(possibleVirtualFilePath, userinfo.Username)
  85. if err != nil {
  86. log.Println("Error when trying to serve file in compatibility mode", err.Error())
  87. return nil, "", errors.New("Error when trying to serve file in compatibility mode")
  88. }
  89. if fshAbs.FileExists(possibleRealpath) {
  90. realFilepath = possibleRealpath
  91. log.Println("[Media Server] Serving file " + filepath.Base(possibleRealpath) + " in compatibility mode. Do not to use '&' or '+' sign in filename! ")
  92. return fsh, realFilepath, nil
  93. } else {
  94. return nil, "", errors.New("File not exists")
  95. }
  96. }
  97. return fsh, realFilepath, nil
  98. }
  99. func serveMediaMime(w http.ResponseWriter, r *http.Request) {
  100. targetFsh, realFilepath, err := media_server_validateSourceFile(w, r)
  101. if err != nil {
  102. common.SendErrorResponse(w, err.Error())
  103. return
  104. }
  105. targetFshAbs := targetFsh.FileSystemAbstraction
  106. if targetFsh.RequireBuffer {
  107. //File is not on local. Guess its mime by extension
  108. common.SendTextResponse(w, "application/"+filepath.Ext(realFilepath)[1:])
  109. return
  110. }
  111. mime := "text/directory"
  112. if !targetFshAbs.IsDir(realFilepath) {
  113. m, _, err := fs.GetMime(realFilepath)
  114. if err != nil {
  115. mime = ""
  116. }
  117. mime = m
  118. }
  119. common.SendTextResponse(w, mime)
  120. }
  121. func serverMedia(w http.ResponseWriter, r *http.Request) {
  122. //Serve normal media files
  123. targetFsh, realFilepath, err := media_server_validateSourceFile(w, r)
  124. if err != nil {
  125. common.SendErrorResponse(w, err.Error())
  126. return
  127. }
  128. targetFshAbs := targetFsh.FileSystemAbstraction
  129. //Check if downloadMode
  130. downloadMode := false
  131. dw, _ := common.Mv(r, "download", false)
  132. if dw == "true" {
  133. downloadMode = true
  134. }
  135. //New download implementations, allow /download to be used instead of &download=true
  136. if strings.Contains(r.RequestURI, "media/download/?file=") {
  137. downloadMode = true
  138. }
  139. //Serve the file
  140. if downloadMode {
  141. escapedRealFilepath, err := url.PathUnescape(realFilepath)
  142. if err != nil {
  143. common.SendErrorResponse(w, err.Error())
  144. return
  145. }
  146. filename := filepath.Base(escapedRealFilepath)
  147. /*
  148. //12 Jul 2022 Update: Deprecated the browser detection logic
  149. userAgent := r.Header.Get("User-Agent")
  150. if strings.Contains(userAgent, "Safari/")) {
  151. //This is Safari. Use speial header
  152. w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(realFilepath))
  153. } else {
  154. //Fixing the header issue on Golang url encode lib problems
  155. w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+filename)
  156. }
  157. */
  158. w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
  159. w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
  160. if targetFsh.RequireBuffer || !filesystem.FileExists(realFilepath) {
  161. //Stream it directly from remote
  162. w.Header().Set("Content-Length", strconv.Itoa(int(targetFshAbs.GetFileSize(realFilepath))))
  163. remoteStream, err := targetFshAbs.ReadStream(realFilepath)
  164. if err != nil {
  165. common.SendErrorResponse(w, err.Error())
  166. return
  167. }
  168. io.Copy(w, remoteStream)
  169. remoteStream.Close()
  170. } else {
  171. http.ServeFile(w, r, escapedRealFilepath)
  172. }
  173. } else {
  174. if targetFsh.RequireBuffer || !filesystem.FileExists(realFilepath) {
  175. w.Header().Set("Content-Length", strconv.Itoa(int(targetFshAbs.GetFileSize(realFilepath))))
  176. remoteStream, err := targetFshAbs.ReadStream(realFilepath)
  177. if err != nil {
  178. common.SendErrorResponse(w, err.Error())
  179. return
  180. }
  181. io.Copy(w, remoteStream)
  182. remoteStream.Close()
  183. } else {
  184. http.ServeFile(w, r, realFilepath)
  185. }
  186. }
  187. }