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