284 lines
6.9 KiB
Go
284 lines
6.9 KiB
Go
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 := `<?xml version="1.0" encoding="utf-8"?>` +
|
|
`<D:multistatus xmlns:D="DAV:">`
|
|
|
|
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 += `</D:multistatus>`
|
|
|
|
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 "" +
|
|
"<D:response>" +
|
|
"<D:href>" + xmlEscape(href) + "</D:href>" +
|
|
"<D:propstat>" +
|
|
"<D:prop>" +
|
|
"<D:displayname>" + xmlEscape(name) + "</D:displayname>" +
|
|
"<D:getcontentlength>" + strconv.FormatInt(info.Size(), 10) + "</D:getcontentlength>" +
|
|
"<D:getlastmodified>" + xmlEscape(info.ModTime().UTC().Format(http.TimeFormat)) + "</D:getlastmodified>" +
|
|
"<D:resourcetype/>" +
|
|
"</D:prop>" +
|
|
"<D:status>HTTP/1.1 200 OK</D:status>" +
|
|
"</D:propstat>" +
|
|
"</D:response>"
|
|
}
|
|
|
|
func buildDirPropResponse(abs_path string, href string, info os.FileInfo, max_depth int) (string, error) {
|
|
name := info.Name()
|
|
|
|
xml := "" +
|
|
"<D:response>" +
|
|
"<D:href>" + xmlEscape(ensureTrailingSlash(href)) + "</D:href>" +
|
|
"<D:propstat>" +
|
|
"<D:prop>" +
|
|
"<D:displayname>" + xmlEscape(name) + "</D:displayname>" +
|
|
"<D:resourcetype><D:collection/></D:resourcetype>" +
|
|
"<D:getlastmodified>" + xmlEscape(info.ModTime().UTC().Format(http.TimeFormat)) + "</D:getlastmodified>" +
|
|
"</D:prop>" +
|
|
"<D:status>HTTP/1.1 200 OK</D:status>" +
|
|
"</D:propstat>" +
|
|
"</D:response>"
|
|
|
|
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
|
|
}
|
|
}
|