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
}
}