package storageService import ( "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/xml" "io" "net/http" "net/url" "os" "path" "path/filepath" "strconv" "strings" "time" "git.ma-al.com/goc_daniel/b2b/app/model" "git.ma-al.com/goc_daniel/b2b/app/repos/storageRepo" constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data" "git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors" ) type StorageService struct { storageRepo storageRepo.UIStorageRepo } func New() *StorageService { return &StorageService{ storageRepo: storageRepo.New(), } } func (s *StorageService) EntryInfo(abs_path string) (os.FileInfo, error) { return s.storageRepo.EntryInfo(abs_path) } func (s *StorageService) NewWebdavToken(user_id uint) (string, error) { b := make([]byte, constdata.NBYTES_IN_WEBDAV_TOKEN) _, err := rand.Read(b) if err != nil { return "", err } raw_token := hex.EncodeToString(b) hash_token_bytes := sha256.Sum256([]byte(raw_token)) hash_token := hex.EncodeToString(hash_token_bytes[:]) expires_at := time.Now().Add(24 * time.Hour) return raw_token, s.storageRepo.SaveWebdavToken(user_id, hash_token, &expires_at) } func (s *StorageService) DownloadFilePrep(abs_path string) (*os.File, string, int64, error) { info, err := s.storageRepo.EntryInfo(abs_path) if err != nil || info.IsDir() { return nil, "", 0, responseErrors.ErrFileDoesNotExist } f, err := s.storageRepo.OpenFile(abs_path) if err != nil { return nil, "", 0, err } return f, filepath.Base(abs_path), info.Size(), nil } func (s *StorageService) ListContent(abs_path string) (*[]model.EntryInList, error) { info, err := s.storageRepo.EntryInfo(abs_path) if err != nil || !info.IsDir() { return nil, responseErrors.ErrFolderDoesNotExist } entries_in_list, err := s.storageRepo.ListContent(abs_path) return entries_in_list, err } func (s *StorageService) Propfind(root string, abs_path string, depth string) (string, error) { href := href(root, abs_path) max_depth := 0 switch depth { case "0": max_depth = 0 case "1": max_depth = 1 case "infinity": max_depth = 32 default: max_depth = 0 } info, err := s.storageRepo.EntryInfo(abs_path) if err != nil { return "", err } xml := `` + `` if info.IsDir() { href = ensureTrailingSlash(href) next_xml, err := buildDirPropResponse(abs_path, href, info, max_depth) if err != nil { return "", err } xml += next_xml } else { xml += buildFilePropResponse(href, info) } xml += `` return xml, nil } func (s *StorageService) Put(abs_path string, src io.Reader) error { return s.storageRepo.Put(abs_path, src) } func (s *StorageService) Delete(abs_path string) error { return s.storageRepo.Delete(abs_path) } func (s *StorageService) Mkcol(abs_path string) error { _, err := s.storageRepo.EntryInfo(abs_path) if err == nil { return responseErrors.ErrNameTaken } else if os.IsNotExist(err) { return s.storageRepo.Mkcol(abs_path) } else { return err } } func (s *StorageService) Move(src_abs_path string, dest_abs_path string) error { return s.storageRepo.Move(src_abs_path, dest_abs_path) } func (s *StorageService) Copy(src_abs_path string, dest_abs_path string) error { return s.storageRepo.Copy(src_abs_path, dest_abs_path) } func buildFilePropResponse(href string, info os.FileInfo) string { name := info.Name() return "" + "" + "" + xmlEscape(href) + "" + "" + "" + "" + xmlEscape(name) + "" + "" + strconv.FormatInt(info.Size(), 10) + "" + "" + xmlEscape(info.ModTime().UTC().Format(http.TimeFormat)) + "" + "" + "" + "HTTP/1.1 200 OK" + "" + "" } func buildDirPropResponse(abs_path string, href string, info os.FileInfo, max_depth int) (string, error) { name := info.Name() xml := "" + "" + "" + xmlEscape(ensureTrailingSlash(href)) + "" + "" + "" + "" + xmlEscape(name) + "" + "" + "" + xmlEscape(info.ModTime().UTC().Format(http.TimeFormat)) + "" + "" + "HTTP/1.1 200 OK" + "" + "" if max_depth <= 0 { return xml, nil } entries, err := os.ReadDir(abs_path) if err != nil { return "", err } for _, entry := range entries { child_abs_path := filepath.Join(abs_path, entry.Name()) child_href := path.Join(href, entry.Name()) child_info, err := entry.Info() if err != nil { return "", err } var xml_next string if entry.IsDir() { xml_next, err = buildDirPropResponse(child_abs_path, ensureTrailingSlash(child_href), child_info, max_depth-1) } else { xml_next = buildFilePropResponse(child_href, child_info) } if err != nil { return "", err } xml += xml_next } return xml, nil } func ensureTrailingSlash(s string) string { if s == "/" { return s } if !strings.HasSuffix(s, "/") { return s + "/" } return s } func xmlEscape(s string) string { var b strings.Builder xml.EscapeText(&b, []byte(s)) return b.String() } // Returns href based on file's absolute path. Doesn't validate abs_path func href(root string, abs_path string) string { rel, _ := filepath.Rel(root, abs_path) if rel == "." { return constdata.WEBDAV_HREF_ROOT + "/" } rel = filepath.ToSlash(rel) parts := strings.Split(rel, "/") for i, p := range parts { parts[i] = url.PathEscape(p) } return strings.TrimRight(constdata.WEBDAV_HREF_ROOT, "/") + "/" + strings.Join(parts, "/") } // AbsPath extracts an absolute path and validates it func (s *StorageService) AbsPath(root string, relative_path string) (string, error) { decoded, err := url.PathUnescape(relative_path) if err != nil { return "", err } clean_name := filepath.Clean(decoded) full_path := filepath.Join(root, clean_name) if full_path != root && !strings.HasPrefix(full_path, root+"/") { return "", responseErrors.ErrAccessDenied } return full_path, nil } // ObtainDestPath extracts the absolute path based on URL absolute path func (s *StorageService) ObtainDestPath(root string, dest_path string) (string, error) { idx := strings.Index(dest_path, constdata.WEBDAV_TRIMMED_ROOT) if idx == -1 { return "", responseErrors.ErrAccessDenied } prefix_removed := dest_path[idx+len(constdata.WEBDAV_TRIMMED_ROOT):] decoded, err := url.PathUnescape(prefix_removed) if err != nil { return "", err } clean_dest_path := filepath.Clean(decoded) if clean_dest_path == "" { return root, nil } else if strings.HasPrefix(clean_dest_path, "/") { return root + "/" + strings.TrimPrefix(clean_dest_path, "/"), nil } else { return "", responseErrors.ErrAccessDenied } }