filemanager.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package filemanager
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "imuslab.com/zoraxy/mod/utils"
  12. )
  13. /*
  14. File Manager
  15. This is a simple package that handles file management
  16. under the web server directory
  17. */
  18. type FileManager struct {
  19. Directory string
  20. }
  21. // Create a new file manager with directory as root
  22. func NewFileManager(directory string) *FileManager {
  23. return &FileManager{
  24. Directory: directory,
  25. }
  26. }
  27. // Handle listing of a given directory
  28. func (fm *FileManager) HandleList(w http.ResponseWriter, r *http.Request) {
  29. directory, err := utils.GetPara(r, "dir")
  30. if err != nil {
  31. utils.SendErrorResponse(w, "invalid directory given")
  32. return
  33. }
  34. // Construct the absolute path to the target directory
  35. targetDir := filepath.Join(fm.Directory, directory)
  36. // Clean path to prevent path escape #274
  37. targetDir = filepath.ToSlash(filepath.Clean(targetDir))
  38. targetDir = strings.ReplaceAll(targetDir, "../", "")
  39. // Open the target directory
  40. dirEntries, err := os.ReadDir(targetDir)
  41. if err != nil {
  42. utils.SendErrorResponse(w, "unable to open directory")
  43. return
  44. }
  45. // Create a slice to hold the file information
  46. var files []map[string]interface{} = []map[string]interface{}{}
  47. // Iterate through the directory entries
  48. for _, dirEntry := range dirEntries {
  49. fileInfo := make(map[string]interface{})
  50. fileInfo["filename"] = dirEntry.Name()
  51. fileInfo["filepath"] = filepath.Join(directory, dirEntry.Name())
  52. fileInfo["isDir"] = dirEntry.IsDir()
  53. // Get file size and last modified time
  54. finfo, err := dirEntry.Info()
  55. if err != nil {
  56. //unable to load its info. Skip this file
  57. continue
  58. }
  59. fileInfo["lastModified"] = finfo.ModTime().Unix()
  60. if !dirEntry.IsDir() {
  61. // If it's a file, get its size
  62. fileInfo["size"] = finfo.Size()
  63. } else {
  64. // If it's a directory, set size to 0
  65. fileInfo["size"] = 0
  66. }
  67. // Append file info to the list
  68. files = append(files, fileInfo)
  69. }
  70. // Serialize the file info slice to JSON
  71. jsonData, err := json.Marshal(files)
  72. if err != nil {
  73. utils.SendErrorResponse(w, "unable to marshal JSON")
  74. return
  75. }
  76. // Set response headers and send the JSON response
  77. w.Header().Set("Content-Type", "application/json")
  78. w.WriteHeader(http.StatusOK)
  79. w.Write(jsonData)
  80. }
  81. // Handle upload of a file (multi-part), 25MB max
  82. func (fm *FileManager) HandleUpload(w http.ResponseWriter, r *http.Request) {
  83. dir, err := utils.PostPara(r, "dir")
  84. if err != nil {
  85. log.Println("no dir given")
  86. utils.SendErrorResponse(w, "invalid dir given")
  87. return
  88. }
  89. // Parse the multi-part form data
  90. err = r.ParseMultipartForm(25 << 20)
  91. if err != nil {
  92. utils.SendErrorResponse(w, "unable to parse form data")
  93. return
  94. }
  95. // Get the uploaded file
  96. file, fheader, err := r.FormFile("file")
  97. if err != nil {
  98. log.Println(err.Error())
  99. utils.SendErrorResponse(w, "unable to get uploaded file")
  100. return
  101. }
  102. defer file.Close()
  103. // Specify the directory where you want to save the uploaded file
  104. uploadDir := filepath.Join(fm.Directory, dir)
  105. if !utils.FileExists(uploadDir) {
  106. utils.SendErrorResponse(w, "upload target directory not exists")
  107. return
  108. }
  109. filename := sanitizeFilename(fheader.Filename)
  110. if !isValidFilename(filename) {
  111. utils.SendErrorResponse(w, "filename contain invalid or reserved characters")
  112. return
  113. }
  114. // Create the file on the server
  115. filePath := filepath.Join(uploadDir, filepath.Base(filename))
  116. out, err := os.Create(filePath)
  117. if err != nil {
  118. utils.SendErrorResponse(w, "unable to create file on the server")
  119. return
  120. }
  121. defer out.Close()
  122. // Copy the uploaded file to the server
  123. _, err = io.Copy(out, file)
  124. if err != nil {
  125. utils.SendErrorResponse(w, "unable to copy file to server")
  126. return
  127. }
  128. // Respond with a success message or appropriate response
  129. utils.SendOK(w)
  130. }
  131. // Handle download of a selected file, serve with content dispose header
  132. func (fm *FileManager) HandleDownload(w http.ResponseWriter, r *http.Request) {
  133. filename, err := utils.GetPara(r, "file")
  134. if err != nil {
  135. utils.SendErrorResponse(w, "invalid filepath given")
  136. return
  137. }
  138. previewMode, _ := utils.GetPara(r, "preview")
  139. if previewMode == "true" {
  140. // Serve the file using http.ServeFile
  141. filePath := filepath.Join(fm.Directory, filename)
  142. http.ServeFile(w, r, filePath)
  143. } else {
  144. // Trigger a download with content disposition headers
  145. filePath := filepath.Join(fm.Directory, filename)
  146. w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(filename))
  147. http.ServeFile(w, r, filePath)
  148. }
  149. }
  150. // HandleNewFolder creates a new folder in the specified directory
  151. func (fm *FileManager) HandleNewFolder(w http.ResponseWriter, r *http.Request) {
  152. // Parse the directory name from the request
  153. dirName, err := utils.PostPara(r, "path")
  154. if err != nil {
  155. utils.SendErrorResponse(w, "invalid directory name")
  156. return
  157. }
  158. //Prevent path escape
  159. dirName = strings.ReplaceAll(dirName, "\\", "/")
  160. dirName = strings.ReplaceAll(dirName, "../", "")
  161. // Specify the directory where you want to create the new folder
  162. newFolderPath := filepath.Join(fm.Directory, dirName)
  163. // Check if the folder already exists
  164. if _, err := os.Stat(newFolderPath); os.IsNotExist(err) {
  165. // Create the new folder
  166. err := os.Mkdir(newFolderPath, os.ModePerm)
  167. if err != nil {
  168. utils.SendErrorResponse(w, "unable to create the new folder")
  169. return
  170. }
  171. // Respond with a success message or appropriate response
  172. utils.SendOK(w)
  173. } else {
  174. // If the folder already exists, respond with an error
  175. utils.SendErrorResponse(w, "folder already exists")
  176. }
  177. }
  178. // HandleFileCopy copies a file or directory from the source path to the destination path
  179. func (fm *FileManager) HandleFileCopy(w http.ResponseWriter, r *http.Request) {
  180. // Parse the source and destination paths from the request
  181. srcPath, err := utils.PostPara(r, "srcpath")
  182. if err != nil {
  183. utils.SendErrorResponse(w, "invalid source path")
  184. return
  185. }
  186. destPath, err := utils.PostPara(r, "destpath")
  187. if err != nil {
  188. utils.SendErrorResponse(w, "invalid destination path")
  189. return
  190. }
  191. // Validate and sanitize the source and destination paths
  192. srcPath = filepath.Clean(srcPath)
  193. destPath = filepath.Clean(destPath)
  194. // Construct the absolute paths
  195. absSrcPath := filepath.Join(fm.Directory, srcPath)
  196. absDestPath := filepath.Join(fm.Directory, destPath)
  197. // Check if the source path exists
  198. if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
  199. utils.SendErrorResponse(w, "source path does not exist")
  200. return
  201. }
  202. // Check if the destination path exists
  203. if _, err := os.Stat(absDestPath); os.IsNotExist(err) {
  204. utils.SendErrorResponse(w, "destination path does not exist")
  205. return
  206. }
  207. //Join the name to create final paste filename
  208. absDestPath = filepath.Join(absDestPath, filepath.Base(absSrcPath))
  209. //Reject opr if already exists
  210. if utils.FileExists(absDestPath) {
  211. utils.SendErrorResponse(w, "target already exists")
  212. return
  213. }
  214. // Perform the copy operation based on whether the source is a file or directory
  215. if isDir(absSrcPath) {
  216. // Recursive copy for directories
  217. err := copyDirectory(absSrcPath, absDestPath)
  218. if err != nil {
  219. utils.SendErrorResponse(w, fmt.Sprintf("error copying directory: %v", err))
  220. return
  221. }
  222. } else {
  223. // Copy a single file
  224. err := copyFile(absSrcPath, absDestPath)
  225. if err != nil {
  226. utils.SendErrorResponse(w, fmt.Sprintf("error copying file: %v", err))
  227. return
  228. }
  229. }
  230. utils.SendOK(w)
  231. }
  232. func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) {
  233. // Parse the source and destination paths from the request
  234. srcPath, err := utils.PostPara(r, "srcpath")
  235. if err != nil {
  236. utils.SendErrorResponse(w, "invalid source path")
  237. return
  238. }
  239. destPath, err := utils.PostPara(r, "destpath")
  240. if err != nil {
  241. utils.SendErrorResponse(w, "invalid destination path")
  242. return
  243. }
  244. // Validate and sanitize the source and destination paths
  245. srcPath = filepath.Clean(srcPath)
  246. destPath = filepath.Clean(destPath)
  247. // Construct the absolute paths
  248. absSrcPath := filepath.Join(fm.Directory, srcPath)
  249. absDestPath := filepath.Join(fm.Directory, destPath)
  250. // Check if the source path exists
  251. if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
  252. utils.SendErrorResponse(w, "source path does not exist")
  253. return
  254. }
  255. // Check if the destination path exists
  256. if _, err := os.Stat(absDestPath); !os.IsNotExist(err) {
  257. utils.SendErrorResponse(w, "destination path already exists")
  258. return
  259. }
  260. // Rename the source to the destination
  261. err = os.Rename(absSrcPath, absDestPath)
  262. if err != nil {
  263. utils.SendErrorResponse(w, fmt.Sprintf("error moving file/directory: %v", err))
  264. return
  265. }
  266. utils.SendOK(w)
  267. }
  268. func (fm *FileManager) HandleFileProperties(w http.ResponseWriter, r *http.Request) {
  269. // Parse the target file or directory path from the request
  270. filePath, err := utils.GetPara(r, "file")
  271. if err != nil {
  272. utils.SendErrorResponse(w, "invalid file path")
  273. return
  274. }
  275. // Construct the absolute path to the target file or directory
  276. absPath := filepath.Join(fm.Directory, filePath)
  277. // Check if the target path exists
  278. _, err = os.Stat(absPath)
  279. if err != nil {
  280. utils.SendErrorResponse(w, "file or directory does not exist")
  281. return
  282. }
  283. // Initialize a map to hold file properties
  284. fileProps := make(map[string]interface{})
  285. fileProps["filename"] = filepath.Base(absPath)
  286. fileProps["filepath"] = filePath
  287. fileProps["isDir"] = isDir(absPath)
  288. // Get file size and last modified time
  289. finfo, err := os.Stat(absPath)
  290. if err != nil {
  291. utils.SendErrorResponse(w, "unable to retrieve file properties")
  292. return
  293. }
  294. fileProps["lastModified"] = finfo.ModTime().Unix()
  295. if !isDir(absPath) {
  296. // If it's a file, get its size
  297. fileProps["size"] = finfo.Size()
  298. } else {
  299. // If it's a directory, calculate its total size containing all child files and folders
  300. totalSize, err := calculateDirectorySize(absPath)
  301. if err != nil {
  302. utils.SendErrorResponse(w, "unable to calculate directory size")
  303. return
  304. }
  305. fileProps["size"] = totalSize
  306. }
  307. // Count the number of sub-files and sub-folders
  308. numSubFiles, numSubFolders, err := countSubFilesAndFolders(absPath)
  309. if err != nil {
  310. utils.SendErrorResponse(w, "unable to count sub-files and sub-folders")
  311. return
  312. }
  313. fileProps["fileCounts"] = numSubFiles
  314. fileProps["folderCounts"] = numSubFolders
  315. // Serialize the file properties to JSON
  316. jsonData, err := json.Marshal(fileProps)
  317. if err != nil {
  318. utils.SendErrorResponse(w, "unable to marshal JSON")
  319. return
  320. }
  321. // Set response headers and send the JSON response
  322. w.Header().Set("Content-Type", "application/json")
  323. w.WriteHeader(http.StatusOK)
  324. w.Write(jsonData)
  325. }
  326. // HandleFileDelete deletes a file or directory
  327. func (fm *FileManager) HandleFileDelete(w http.ResponseWriter, r *http.Request) {
  328. // Parse the target file or directory path from the request
  329. filePath, err := utils.PostPara(r, "target")
  330. if err != nil {
  331. utils.SendErrorResponse(w, "invalid file path")
  332. return
  333. }
  334. // Construct the absolute path to the target file or directory
  335. absPath := filepath.Join(fm.Directory, filePath)
  336. // Check if the target path exists
  337. _, err = os.Stat(absPath)
  338. if err != nil {
  339. utils.SendErrorResponse(w, "file or directory does not exist")
  340. return
  341. }
  342. // Delete the file or directory
  343. err = os.RemoveAll(absPath)
  344. if err != nil {
  345. utils.SendErrorResponse(w, "error deleting file or directory")
  346. return
  347. }
  348. // Respond with a success message or appropriate response
  349. utils.SendOK(w)
  350. }