1
0

filemanager.go 11 KB

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