webdavfs.go 10 KB

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