file.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. // SiYuan - Refactor your thinking
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package api
  17. import (
  18. "fmt"
  19. "io"
  20. "mime"
  21. "mime/multipart"
  22. "net/http"
  23. "os"
  24. "path/filepath"
  25. "strconv"
  26. "time"
  27. "github.com/88250/gulu"
  28. "github.com/gabriel-vasile/mimetype"
  29. "github.com/gin-gonic/gin"
  30. "github.com/siyuan-note/filelock"
  31. "github.com/siyuan-note/logging"
  32. "github.com/siyuan-note/siyuan/kernel/model"
  33. "github.com/siyuan-note/siyuan/kernel/util"
  34. )
  35. func getUniqueFilename(c *gin.Context) {
  36. ret := gulu.Ret.NewResult()
  37. defer c.JSON(http.StatusOK, ret)
  38. arg, ok := util.JsonArg(c, ret)
  39. if !ok {
  40. return
  41. }
  42. filePath := arg["path"].(string)
  43. ret.Data = map[string]interface{}{
  44. "path": util.GetUniqueFilename(filePath),
  45. }
  46. }
  47. func globalCopyFiles(c *gin.Context) {
  48. ret := gulu.Ret.NewResult()
  49. defer c.JSON(http.StatusOK, ret)
  50. arg, ok := util.JsonArg(c, ret)
  51. if !ok {
  52. return
  53. }
  54. var srcs []string
  55. srcsArg := arg["srcs"].([]interface{})
  56. for _, s := range srcsArg {
  57. srcs = append(srcs, s.(string))
  58. }
  59. for _, src := range srcs {
  60. if !filelock.IsExist(src) {
  61. msg := fmt.Sprintf("file [%s] does not exist", src)
  62. logging.LogErrorf(msg)
  63. ret.Code = -1
  64. ret.Msg = msg
  65. return
  66. }
  67. }
  68. destDir := arg["destDir"].(string) // 相对于工作空间的路径
  69. destDir = filepath.Join(util.WorkspaceDir, destDir)
  70. for _, src := range srcs {
  71. dest := filepath.Join(destDir, filepath.Base(src))
  72. if err := filelock.Copy(src, dest); nil != err {
  73. logging.LogErrorf("copy file [%s] to [%s] failed: %s", src, dest, err)
  74. ret.Code = -1
  75. ret.Msg = err.Error()
  76. return
  77. }
  78. }
  79. }
  80. func copyFile(c *gin.Context) {
  81. ret := gulu.Ret.NewResult()
  82. defer c.JSON(http.StatusOK, ret)
  83. arg, ok := util.JsonArg(c, ret)
  84. if !ok {
  85. return
  86. }
  87. src := arg["src"].(string)
  88. src, err := model.GetAssetAbsPath(src)
  89. if nil != err {
  90. logging.LogErrorf("get asset [%s] abs path failed: %s", src, err)
  91. ret.Code = -1
  92. ret.Msg = err.Error()
  93. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  94. return
  95. }
  96. info, err := os.Stat(src)
  97. if nil != err {
  98. logging.LogErrorf("stat [%s] failed: %s", src, err)
  99. ret.Code = -1
  100. ret.Msg = err.Error()
  101. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  102. return
  103. }
  104. if info.IsDir() {
  105. ret.Code = -1
  106. ret.Msg = "file is a directory"
  107. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  108. return
  109. }
  110. dest := arg["dest"].(string)
  111. if err = filelock.Copy(src, dest); nil != err {
  112. logging.LogErrorf("copy file [%s] to [%s] failed: %s", src, dest, err)
  113. ret.Code = -1
  114. ret.Msg = err.Error()
  115. ret.Data = map[string]interface{}{"closeTimeout": 5000}
  116. return
  117. }
  118. }
  119. func getFile(c *gin.Context) {
  120. ret := gulu.Ret.NewResult()
  121. arg, ok := util.JsonArg(c, ret)
  122. if !ok {
  123. ret.Code = -1
  124. c.JSON(http.StatusAccepted, ret)
  125. return
  126. }
  127. filePath := arg["path"].(string)
  128. fileAbsPath, err := util.GetAbsPathInWorkspace(filePath)
  129. if nil != err {
  130. ret.Code = http.StatusForbidden
  131. ret.Msg = err.Error()
  132. c.JSON(http.StatusAccepted, ret)
  133. return
  134. }
  135. info, err := os.Stat(fileAbsPath)
  136. if os.IsNotExist(err) {
  137. ret.Code = http.StatusNotFound
  138. ret.Msg = err.Error()
  139. c.JSON(http.StatusAccepted, ret)
  140. return
  141. }
  142. if nil != err {
  143. logging.LogErrorf("stat [%s] failed: %s", fileAbsPath, err)
  144. ret.Code = http.StatusInternalServerError
  145. ret.Msg = err.Error()
  146. c.JSON(http.StatusAccepted, ret)
  147. return
  148. }
  149. if info.IsDir() {
  150. logging.LogErrorf("path [%s] is a directory path", fileAbsPath)
  151. ret.Code = http.StatusMethodNotAllowed
  152. ret.Msg = "This is a directory path"
  153. c.JSON(http.StatusAccepted, ret)
  154. return
  155. }
  156. // REF: https://github.com/siyuan-note/siyuan/issues/11364
  157. if role := model.GetGinContextRole(c); !model.IsValidRole(role, []model.Role{
  158. model.RoleAdministrator,
  159. }) {
  160. if relPath, err := filepath.Rel(util.ConfDir, fileAbsPath); err != nil {
  161. logging.LogErrorf("Get a relative path from [%s] to [%s] failed: %s", util.ConfDir, fileAbsPath, err)
  162. ret.Code = http.StatusInternalServerError
  163. ret.Msg = err.Error()
  164. c.JSON(http.StatusAccepted, ret)
  165. return
  166. } else if relPath == "conf.json" {
  167. ret.Code = http.StatusForbidden
  168. ret.Msg = http.StatusText(http.StatusForbidden)
  169. c.JSON(http.StatusAccepted, ret)
  170. return
  171. }
  172. }
  173. data, err := filelock.ReadFile(fileAbsPath)
  174. if nil != err {
  175. logging.LogErrorf("read file [%s] failed: %s", fileAbsPath, err)
  176. ret.Code = http.StatusInternalServerError
  177. ret.Msg = err.Error()
  178. c.JSON(http.StatusAccepted, ret)
  179. return
  180. }
  181. contentType := mime.TypeByExtension(filepath.Ext(fileAbsPath))
  182. if "" == contentType {
  183. if m := mimetype.Detect(data); nil != m {
  184. contentType = m.String()
  185. }
  186. }
  187. if "" == contentType {
  188. contentType = "application/octet-stream"
  189. }
  190. c.Data(http.StatusOK, contentType, data)
  191. }
  192. func readDir(c *gin.Context) {
  193. ret := gulu.Ret.NewResult()
  194. defer c.JSON(http.StatusOK, ret)
  195. arg, ok := util.JsonArg(c, ret)
  196. if !ok {
  197. c.JSON(http.StatusOK, ret)
  198. return
  199. }
  200. dirPath := arg["path"].(string)
  201. dirAbsPath, err := util.GetAbsPathInWorkspace(dirPath)
  202. if nil != err {
  203. ret.Code = http.StatusForbidden
  204. ret.Msg = err.Error()
  205. return
  206. }
  207. info, err := os.Stat(dirAbsPath)
  208. if os.IsNotExist(err) {
  209. ret.Code = http.StatusNotFound
  210. ret.Msg = err.Error()
  211. return
  212. }
  213. if nil != err {
  214. logging.LogErrorf("stat [%s] failed: %s", dirAbsPath, err)
  215. ret.Code = http.StatusInternalServerError
  216. ret.Msg = err.Error()
  217. return
  218. }
  219. if !info.IsDir() {
  220. logging.LogErrorf("file [%s] is not a directory", dirAbsPath)
  221. ret.Code = http.StatusMethodNotAllowed
  222. ret.Msg = "file is not a directory"
  223. return
  224. }
  225. entries, err := os.ReadDir(dirAbsPath)
  226. if nil != err {
  227. logging.LogErrorf("read dir [%s] failed: %s", dirAbsPath, err)
  228. ret.Code = http.StatusInternalServerError
  229. ret.Msg = err.Error()
  230. return
  231. }
  232. files := []map[string]interface{}{}
  233. for _, entry := range entries {
  234. path := filepath.Join(dirAbsPath, entry.Name())
  235. info, err = os.Stat(path)
  236. if nil != err {
  237. logging.LogErrorf("stat [%s] failed: %s", path, err)
  238. ret.Code = http.StatusInternalServerError
  239. ret.Msg = err.Error()
  240. return
  241. }
  242. files = append(files, map[string]interface{}{
  243. "name": entry.Name(),
  244. "isDir": info.IsDir(),
  245. "isSymlink": util.IsSymlink(entry),
  246. "updated": info.ModTime().Unix(),
  247. })
  248. }
  249. ret.Data = files
  250. }
  251. func renameFile(c *gin.Context) {
  252. ret := gulu.Ret.NewResult()
  253. defer c.JSON(http.StatusOK, ret)
  254. arg, ok := util.JsonArg(c, ret)
  255. if !ok {
  256. c.JSON(http.StatusOK, ret)
  257. return
  258. }
  259. srcPath := arg["path"].(string)
  260. srcAbsPath, err := util.GetAbsPathInWorkspace(srcPath)
  261. if nil != err {
  262. ret.Code = http.StatusForbidden
  263. ret.Msg = err.Error()
  264. return
  265. }
  266. if !filelock.IsExist(srcAbsPath) {
  267. ret.Code = http.StatusNotFound
  268. ret.Msg = "the [path] file or directory does not exist"
  269. return
  270. }
  271. destPath := arg["newPath"].(string)
  272. destAbsPath, err := util.GetAbsPathInWorkspace(destPath)
  273. if nil != err {
  274. ret.Code = http.StatusForbidden
  275. ret.Msg = err.Error()
  276. c.JSON(http.StatusAccepted, ret)
  277. return
  278. }
  279. if filelock.IsExist(destAbsPath) {
  280. ret.Code = http.StatusConflict
  281. ret.Msg = "the [newPath] file or directory already exists"
  282. return
  283. }
  284. if err := filelock.Rename(srcAbsPath, destAbsPath); nil != err {
  285. logging.LogErrorf("rename file [%s] to [%s] failed: %s", srcAbsPath, destAbsPath, err)
  286. ret.Code = http.StatusInternalServerError
  287. ret.Msg = err.Error()
  288. return
  289. }
  290. }
  291. func removeFile(c *gin.Context) {
  292. ret := gulu.Ret.NewResult()
  293. defer c.JSON(http.StatusOK, ret)
  294. arg, ok := util.JsonArg(c, ret)
  295. if !ok {
  296. c.JSON(http.StatusOK, ret)
  297. return
  298. }
  299. filePath := arg["path"].(string)
  300. fileAbsPath, err := util.GetAbsPathInWorkspace(filePath)
  301. if nil != err {
  302. ret.Code = http.StatusForbidden
  303. ret.Msg = err.Error()
  304. return
  305. }
  306. _, err = os.Stat(fileAbsPath)
  307. if os.IsNotExist(err) {
  308. ret.Code = http.StatusNotFound
  309. return
  310. }
  311. if nil != err {
  312. logging.LogErrorf("stat [%s] failed: %s", fileAbsPath, err)
  313. ret.Code = http.StatusInternalServerError
  314. ret.Msg = err.Error()
  315. return
  316. }
  317. if err = filelock.Remove(fileAbsPath); nil != err {
  318. logging.LogErrorf("remove [%s] failed: %s", fileAbsPath, err)
  319. ret.Code = http.StatusInternalServerError
  320. ret.Msg = err.Error()
  321. return
  322. }
  323. }
  324. func putFile(c *gin.Context) {
  325. ret := gulu.Ret.NewResult()
  326. defer c.JSON(http.StatusOK, ret)
  327. var err error
  328. filePath := c.PostForm("path")
  329. fileAbsPath, err := util.GetAbsPathInWorkspace(filePath)
  330. if nil != err {
  331. ret.Code = http.StatusForbidden
  332. ret.Msg = err.Error()
  333. return
  334. }
  335. isDirStr := c.PostForm("isDir")
  336. isDir, _ := strconv.ParseBool(isDirStr)
  337. if isDir {
  338. err = os.MkdirAll(fileAbsPath, 0755)
  339. if nil != err {
  340. logging.LogErrorf("make dir [%s] failed: %s", fileAbsPath, err)
  341. }
  342. } else {
  343. fileHeader, _ := c.FormFile("file")
  344. if nil == fileHeader {
  345. logging.LogErrorf("form file is nil [path=%s]", fileAbsPath)
  346. ret.Code = http.StatusBadRequest
  347. ret.Msg = "form file is nil"
  348. return
  349. }
  350. for {
  351. dir := filepath.Dir(fileAbsPath)
  352. if err = os.MkdirAll(dir, 0755); nil != err {
  353. logging.LogErrorf("put file [%s] make dir [%s] failed: %s", fileAbsPath, dir, err)
  354. break
  355. }
  356. var f multipart.File
  357. f, err = fileHeader.Open()
  358. if nil != err {
  359. logging.LogErrorf("open file failed: %s", err)
  360. break
  361. }
  362. var data []byte
  363. data, err = io.ReadAll(f)
  364. if nil != err {
  365. logging.LogErrorf("read file failed: %s", err)
  366. break
  367. }
  368. err = filelock.WriteFile(fileAbsPath, data)
  369. if nil != err {
  370. logging.LogErrorf("write file [%s] failed: %s", fileAbsPath, err)
  371. break
  372. }
  373. break
  374. }
  375. }
  376. if nil != err {
  377. ret.Code = -1
  378. ret.Msg = err.Error()
  379. return
  380. }
  381. modTimeStr := c.PostForm("modTime")
  382. modTime := time.Now()
  383. if "" != modTimeStr {
  384. modTimeInt, parseErr := strconv.ParseInt(modTimeStr, 10, 64)
  385. if nil != parseErr {
  386. logging.LogErrorf("parse mod time [%s] failed: %s", modTimeStr, parseErr)
  387. ret.Code = http.StatusInternalServerError
  388. ret.Msg = parseErr.Error()
  389. return
  390. }
  391. modTime = millisecond2Time(modTimeInt)
  392. }
  393. if err = os.Chtimes(fileAbsPath, modTime, modTime); nil != err {
  394. logging.LogErrorf("change time failed: %s", err)
  395. ret.Code = http.StatusInternalServerError
  396. ret.Msg = err.Error()
  397. return
  398. }
  399. }
  400. func millisecond2Time(t int64) time.Time {
  401. sec := t / 1000
  402. msec := t % 1000
  403. return time.Unix(sec, msec*int64(time.Millisecond))
  404. }