ultisuite-backend/internal/nextcloud/dav_path.go
R3D347HR4Y bd7534658a Refactor and enhance unified frontend and API features
- Updated environment configuration to unify frontend for mail and drive under a single service.
- Revised README to reflect changes in frontend setup and routing for the unified application.
- Introduced new API documentation endpoints for better accessibility of API specifications.
- Enhanced drive and mail services with improved handling of file uploads and metadata enrichment.
- Implemented new API token management features, including creation, listing, and revocation of tokens.
- Added tests for new functionalities in drive and mail services to ensure reliability and correctness.
2026-06-07 15:44:30 +02:00

195 lines
4.9 KiB
Go

package nextcloud
import (
"net/url"
"strings"
)
// WebDAVPath builds an encoded WebDAV URL for a logical client path (may contain spaces).
func (c *Client) WebDAVPath(userID, path string) string {
userSeg := url.PathEscape(strings.TrimSpace(userID))
logical := strings.Trim(path, "/")
var encoded string
if logical == "" {
encoded = ""
} else {
parts := strings.Split(logical, "/")
for i, p := range parts {
parts[i] = url.PathEscape(p)
}
encoded = strings.Join(parts, "/")
}
if encoded == "" {
return "/remote.php/dav/files/" + userSeg
}
return "/remote.php/dav/files/" + userSeg + "/" + encoded
}
// SameServerDestinationHeader builds a relative WebDAV Destination header value.
// Nextcloud/Sabre rejects absolute URIs when the host differs from OVERWRITEHOST
// (e.g. internal http://nextcloud:80 vs public http://localhost/cloud).
func SameServerDestinationHeader(davPath string) string {
if !strings.HasPrefix(davPath, "/") {
return "/" + davPath
}
return davPath
}
func decodeDAVSegment(seg string) string {
if seg == "" {
return seg
}
dec, err := url.PathUnescape(seg)
if err != nil {
return seg
}
return dec
}
// decodeDAVPath turns a slash-separated (possibly encoded) path into a logical path.
func decodeDAVPath(p string) string {
p = strings.TrimSpace(p)
p = strings.Trim(p, "/")
if p == "" {
return "/"
}
parts := strings.Split(p, "/")
for i, seg := range parts {
parts[i] = decodeDAVSegment(seg)
}
return "/" + strings.Join(parts, "/")
}
// clientPathFromDAVHref maps a WebDAV href to a logical path (/Folder/My File.docx).
func clientPathFromDAVHref(href string) string {
href = strings.TrimSpace(href)
if href == "" {
return "/"
}
if marker := "/dav/files/"; strings.Contains(href, marker) {
idx := strings.Index(href, marker)
rest := strings.TrimSuffix(href[idx+len(marker):], "/")
slash := strings.Index(rest, "/")
if slash < 0 {
return "/"
}
return decodeDAVPath("/" + rest[slash+1:])
}
if i := strings.LastIndex(href, "/trash/"); i >= 0 {
return decodeDAVPath("/" + href[i+len("/trash/"):])
}
return decodeDAVPath(href)
}
func fileNameFromDAVProp(displayName, href string) string {
if dn := strings.TrimSpace(displayName); dn != "" {
return dn
}
href = strings.TrimSuffix(strings.TrimSpace(href), "/")
if href == "" {
return ""
}
if i := strings.LastIndex(href, "/"); i >= 0 {
href = href[i+1:]
}
return decodeDAVSegment(href)
}
// NormalizeClientPath decodes segments and ensures a leading slash.
func NormalizeClientPath(path string) string {
return decodeDAVPath(path)
}
// JoinClientPath joins a directory path with a file or folder name.
func JoinClientPath(dir, name string) string {
name = strings.TrimSpace(name)
dir = NormalizeClientPath(dir)
if name == "" {
return dir
}
if dir == "/" {
return "/" + name
}
return dir + "/" + name
}
// NormalizeClientFilePath maps OCS/WebDAV paths to logical paths under the user files root.
func NormalizeClientFilePath(userID, path string) string {
path = strings.TrimSpace(path)
if path == "" {
return "/"
}
path = NormalizeClientPath(path)
uid := strings.TrimSpace(userID)
if uid == "" {
return path
}
trimmed := strings.Trim(path, "/")
if trimmed == "" {
return "/"
}
parts := strings.Split(trimmed, "/")
if len(parts) >= 2 {
head := decodeDAVSegment(parts[0])
if head == uid && parts[1] == "files" {
return NormalizeClientPath("/" + strings.Join(parts[2:], "/"))
}
if parts[0] == "files" && decodeDAVSegment(parts[1]) == uid {
return NormalizeClientPath("/" + strings.Join(parts[2:], "/"))
}
}
return path
}
// FileNameFromClientPath returns the storage basename for a logical client path.
func FileNameFromClientPath(path string) string {
return pathBaseName(NormalizeClientPath(path))
}
// SyncFileDisplayName aligns display name with the storage path basename when present.
func SyncFileDisplayName(path, name string) string {
if bn := FileNameFromClientPath(path); bn != "" {
return bn
}
return strings.TrimSpace(name)
}
// EnsureClientFilePath joins name when path is a parent directory (Nextcloud recent API).
func EnsureClientFilePath(path, name string) string {
path = NormalizeClientPath(path)
name = strings.TrimSpace(name)
if name == "" {
return path
}
if path == "/" {
return "/" + name
}
if strings.HasSuffix(path, "/"+name) {
return path
}
base := path[strings.LastIndex(path, "/")+1:]
if base == name {
return path
}
return JoinClientPath(path, name)
}
// ResolvePropfindClientPath resolves a PROPFIND child href against the listed directory.
func ResolvePropfindClientPath(listDir, href, fileName string) string {
if strings.Contains(href, "/dav/files/") {
return clientPathFromDAVHref(href)
}
base := NormalizeClientPath(listDir)
rel := strings.TrimPrefix(clientPathFromDAVHref(href), "/")
if rel == "" {
rel = strings.TrimSpace(fileName)
}
if rel == "" {
return base
}
if base == "/" {
return "/" + rel
}
return base + "/" + rel
}