filemanager.go 12 KB

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