webdavfs.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. package webdavfs
  2. import (
  3. "errors"
  4. "io"
  5. "io/fs"
  6. "log"
  7. "os"
  8. "path/filepath"
  9. "regexp"
  10. "strings"
  11. "time"
  12. "github.com/studio-b12/gowebdav"
  13. "imuslab.com/arozos/mod/filesystem/arozfs"
  14. )
  15. /*
  16. WebDAV Client
  17. This script is design as a wrapper of the studio-b12/gowebdav module
  18. that allow access to webdav network drive in ArozOS and allow arozos
  19. cross-mounting each others
  20. */
  21. type WebDAVFileSystem struct {
  22. UUID string
  23. Hierarchy string
  24. root string
  25. user string
  26. c *gowebdav.Client
  27. }
  28. func NewWebDAVMount(UUID string, Hierarchy string, root string, user string, password string) (*WebDAVFileSystem, error) {
  29. //Connect to webdav server
  30. c := gowebdav.NewClient(root, user, password)
  31. c.SetTimeout(5 * time.Second)
  32. err := c.Connect()
  33. if err != nil {
  34. log.Println("[WebDAV FS] Unable to connect to remote: ", err.Error())
  35. return nil, err
  36. } else {
  37. log.Println("[WebDAV FS] Connected to remote: " + root)
  38. }
  39. return &WebDAVFileSystem{
  40. UUID: UUID,
  41. Hierarchy: Hierarchy,
  42. c: c,
  43. root: root,
  44. user: user,
  45. }, nil
  46. }
  47. func (e WebDAVFileSystem) Chmod(filename string, mode os.FileMode) error {
  48. return errors.New("filesystem type not supported")
  49. }
  50. func (e WebDAVFileSystem) Chown(filename string, uid int, gid int) error {
  51. return errors.New("filesystem type not supported")
  52. }
  53. func (e WebDAVFileSystem) Chtimes(filename string, atime time.Time, mtime time.Time) error {
  54. return errors.New("filesystem type not supported")
  55. }
  56. func (e WebDAVFileSystem) Create(filename string) (arozfs.File, error) {
  57. return nil, errors.New("filesystem type not supported")
  58. }
  59. func (e WebDAVFileSystem) Mkdir(filename string, mode os.FileMode) error {
  60. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  61. return e.c.Mkdir(filename, mode)
  62. }
  63. func (e WebDAVFileSystem) MkdirAll(filename string, mode os.FileMode) error {
  64. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  65. return e.c.MkdirAll(filename, mode)
  66. }
  67. func (e WebDAVFileSystem) Name() string {
  68. return ""
  69. }
  70. func (e WebDAVFileSystem) Open(filename string) (arozfs.File, error) {
  71. return nil, errors.New("filesystem type not supported")
  72. }
  73. func (e WebDAVFileSystem) OpenFile(filename string, flag int, perm os.FileMode) (arozfs.File, error) {
  74. //Buffer the target file to memory
  75. //To be implement: Wait for Golang's fs.File.Write function to be released
  76. //f := bufffs.New(filename)
  77. //return f, nil
  78. return nil, errors.New("filesystem type not supported")
  79. }
  80. func (e WebDAVFileSystem) Remove(filename string) error {
  81. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  82. return e.c.Remove(filename)
  83. }
  84. func (e WebDAVFileSystem) RemoveAll(filename string) error {
  85. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  86. return e.c.RemoveAll(filename)
  87. }
  88. func (e WebDAVFileSystem) Rename(oldname, newname string) error {
  89. oldname = filterFilepath(filepath.ToSlash(filepath.Clean(oldname)))
  90. newname = filterFilepath(filepath.ToSlash(filepath.Clean(newname)))
  91. err := e.c.Rename(oldname, newname, true)
  92. if err != nil {
  93. //Unable to rename due to reverse proxy issue. Use Copy and Delete
  94. f, err := e.c.ReadStream(oldname)
  95. if err != nil {
  96. return err
  97. }
  98. err = e.c.WriteStream(newname, f, 0775)
  99. if err != nil {
  100. return err
  101. }
  102. f.Close()
  103. e.c.RemoveAll(oldname)
  104. }
  105. return nil
  106. }
  107. func (e WebDAVFileSystem) Stat(filename string) (os.FileInfo, error) {
  108. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  109. return e.c.Stat(filename)
  110. }
  111. func (e WebDAVFileSystem) VirtualPathToRealPath(subpath string, username string) (string, error) {
  112. subpath = filterFilepath(filepath.ToSlash(filepath.Clean(subpath)))
  113. if strings.HasPrefix(subpath, e.UUID+":") {
  114. //This is full virtual path. Trim the uuid and correct the subpath
  115. subpath = strings.TrimPrefix(subpath, e.UUID+":")
  116. }
  117. if e.Hierarchy == "user" {
  118. return filepath.ToSlash(filepath.Clean(filepath.Join("users", username, subpath))), nil
  119. } else if e.Hierarchy == "public" {
  120. return filepath.ToSlash(filepath.Clean(subpath)), nil
  121. }
  122. return "", errors.New("unsupported filesystem hierarchy")
  123. }
  124. func (e WebDAVFileSystem) RealPathToVirtualPath(rpath string, username string) (string, error) {
  125. rpath = filterFilepath(filepath.ToSlash(filepath.Clean(rpath)))
  126. if e.Hierarchy == "user" && strings.HasPrefix(rpath, "/users/"+username) {
  127. rpath = strings.TrimPrefix(rpath, "/users/"+username)
  128. }
  129. rpath = filepath.ToSlash(rpath)
  130. if !strings.HasPrefix(rpath, "/") {
  131. rpath = "/" + rpath
  132. }
  133. return e.UUID + ":" + rpath, nil
  134. }
  135. func (e WebDAVFileSystem) FileExists(filename string) bool {
  136. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  137. _, err := e.c.Stat(filename)
  138. if os.IsNotExist(err) || err != nil {
  139. return false
  140. }
  141. return true
  142. }
  143. func (e WebDAVFileSystem) IsDir(filename string) bool {
  144. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  145. s, err := e.c.Stat(filename)
  146. if err != nil {
  147. return false
  148. }
  149. return s.IsDir()
  150. }
  151. // Notes: This is not actual Glob function. This just emulate Glob using ReadDir with max depth 1 layer
  152. func (e WebDAVFileSystem) Glob(wildcard string) ([]string, error) {
  153. wildcard = filepath.ToSlash(filepath.Clean(wildcard))
  154. if !strings.HasPrefix(wildcard, "/") {
  155. //Handle case for listing root, "*"
  156. wildcard = "/" + wildcard
  157. }
  158. chunks := strings.Split(strings.TrimPrefix(wildcard, "/"), "/")
  159. results, err := e.globpath("/", chunks, 0)
  160. return results, err
  161. }
  162. func (e WebDAVFileSystem) GetFileSize(filename string) int64 {
  163. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  164. s, err := e.Stat(filename)
  165. if err != nil {
  166. log.Println(err)
  167. return 0
  168. }
  169. return s.Size()
  170. }
  171. func (e WebDAVFileSystem) GetModTime(filename string) (int64, error) {
  172. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  173. s, err := e.Stat(filename)
  174. if err != nil {
  175. return 0, err
  176. }
  177. return s.ModTime().Unix(), nil
  178. }
  179. func (e WebDAVFileSystem) WriteFile(filename string, content []byte, mode os.FileMode) error {
  180. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  181. return e.c.Write(filename, content, mode)
  182. }
  183. func (e WebDAVFileSystem) ReadFile(filename string) ([]byte, error) {
  184. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  185. bytes, err := e.c.Read(filename)
  186. if err != nil {
  187. return []byte(""), err
  188. }
  189. return bytes, nil
  190. }
  191. func (e WebDAVFileSystem) ReadDir(filename string) ([]fs.DirEntry, error) {
  192. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  193. fis, err := e.c.ReadDir(filename)
  194. if err != nil {
  195. return []fs.DirEntry{}, err
  196. }
  197. dirEntires := []fs.DirEntry{}
  198. for _, fi := range fis {
  199. dirEntires = append(dirEntires, newDirEntryFromFileInfo(fi))
  200. }
  201. return dirEntires, nil
  202. }
  203. func (e WebDAVFileSystem) WriteStream(filename string, stream io.Reader, mode os.FileMode) error {
  204. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  205. return e.c.WriteStream(filename, stream, mode)
  206. }
  207. func (e WebDAVFileSystem) ReadStream(filename string) (io.ReadCloser, error) {
  208. filename = filterFilepath(filepath.ToSlash(filepath.Clean(filename)))
  209. return e.c.ReadStream(filename)
  210. }
  211. func (e WebDAVFileSystem) Walk(rootpath string, walkFn filepath.WalkFunc) error {
  212. rootpath = filepath.ToSlash(filepath.Clean(rootpath))
  213. rootStat, err := e.Stat(rootpath)
  214. err = walkFn(rootpath, rootStat, err)
  215. if err != nil {
  216. return err
  217. }
  218. return e.walk(rootpath, walkFn)
  219. }
  220. func (e WebDAVFileSystem) Close() error {
  221. time.Sleep(500 * time.Millisecond)
  222. return nil
  223. }
  224. func (e WebDAVFileSystem) Heartbeat() error {
  225. _, err := e.c.ReadDir("/")
  226. return err
  227. }
  228. /*
  229. Helper Functions
  230. */
  231. func (e WebDAVFileSystem) walk(thisPath string, walkFun filepath.WalkFunc) error {
  232. files, err := e.c.ReadDir(thisPath)
  233. if err != nil {
  234. return err
  235. }
  236. for _, file := range files {
  237. thisFileFullPath := filepath.ToSlash(filepath.Join(thisPath, file.Name()))
  238. if file.IsDir() {
  239. err = walkFun(thisFileFullPath, file, nil)
  240. if err != nil {
  241. return err
  242. }
  243. err = e.walk(thisFileFullPath, walkFun)
  244. if err != nil {
  245. return err
  246. }
  247. } else {
  248. err = walkFun(thisFileFullPath, file, nil)
  249. if err != nil {
  250. return err
  251. }
  252. }
  253. }
  254. return nil
  255. }
  256. func (e WebDAVFileSystem) globpath(currentPath string, pathSegments []string, depth int) ([]string, error) {
  257. const pathSeparatorsLimit = 1000
  258. if depth == pathSeparatorsLimit {
  259. return nil, errors.New("bad pattern")
  260. }
  261. // Check pattern is well-formed.
  262. if _, err := regexp.MatchString(wildCardToRegexp(strings.Join(pathSegments, "/")), ""); err != nil {
  263. return nil, err
  264. }
  265. if len(pathSegments) == 0 {
  266. //Reaching the bottom
  267. return []string{}, nil
  268. }
  269. thisSegment := pathSegments[0]
  270. if strings.Contains(thisSegment, "*") {
  271. //Search for matching
  272. matchPattern := currentPath + thisSegment
  273. files, err := e.c.ReadDir(currentPath)
  274. if err != nil {
  275. return []string{}, nil
  276. }
  277. //Check which file in the currentPath matches the wildcard
  278. matchedSubpaths := []string{}
  279. for _, file := range files {
  280. thisPath := currentPath + file.Name()
  281. match, _ := regexp.MatchString(wildCardToRegexp(matchPattern), thisPath)
  282. if match {
  283. if file.IsDir() {
  284. matchedSubpaths = append(matchedSubpaths, thisPath+"/")
  285. } else {
  286. matchedSubpaths = append(matchedSubpaths, thisPath)
  287. }
  288. }
  289. }
  290. if len(pathSegments[1:]) == 0 {
  291. return matchedSubpaths, nil
  292. }
  293. //For each of the subpaths, do a globpath
  294. matchingFilenames := []string{}
  295. for _, subpath := range matchedSubpaths {
  296. thisMatchedNames, _ := e.globpath(subpath, pathSegments[1:], depth+1)
  297. matchingFilenames = append(matchingFilenames, thisMatchedNames...)
  298. }
  299. return matchingFilenames, nil
  300. } else {
  301. //Check folder exists
  302. if e.FileExists(currentPath+thisSegment) && e.IsDir(currentPath+thisSegment) {
  303. return e.globpath(currentPath+thisSegment+"/", pathSegments[1:], depth+1)
  304. } else {
  305. //Not matching
  306. return []string{}, nil
  307. }
  308. }
  309. }
  310. func filterFilepath(rawpath string) string {
  311. rawpath = strings.TrimSpace(rawpath)
  312. if strings.HasPrefix(rawpath, "./") {
  313. return rawpath[1:]
  314. } else if rawpath == "." || rawpath == "" {
  315. return "/"
  316. }
  317. return rawpath
  318. }
  319. func wildCardToRegexp(pattern string) string {
  320. var result strings.Builder
  321. for i, literal := range strings.Split(pattern, "*") {
  322. // Replace * with .*
  323. if i > 0 {
  324. result.WriteString(".*")
  325. }
  326. // Quote any regular expression meta characters in the
  327. // literal text.
  328. result.WriteString(regexp.QuoteMeta(literal))
  329. }
  330. return result.String()
  331. }