- 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.
195 lines
4.9 KiB
Go
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
|
|
}
|