package nextcloud import ( "context" "fmt" "path" "strings" ) // PublicShareBinding maps a public link token to owner storage paths. type PublicShareBinding struct { Token string OwnerID string SharePath string // OCS path, e.g. /Documents/hello.docx or /Documents ItemType string // file | folder } func (b *PublicShareBinding) IsSingleFile() bool { return strings.EqualFold(strings.TrimSpace(b.ItemType), "file") } // ClientSourcePath resolves the logical client path for a shared item. // Single-file shares expose WebDAV at "/" but editors use /filename. func (b *PublicShareBinding) ClientSourcePath(clientPath, displayName string) string { clientPath = NormalizeClientPath(clientPath) if clientPath != "/" { return clientPath } if b != nil && b.IsSingleFile() && b.SharePath != "" { return NormalizeClientPath("/" + path.Base(strings.TrimPrefix(b.SharePath, "/"))) } if name := strings.TrimSpace(displayName); name != "" { return NormalizeClientPath("/" + name) } return "/" } // OwnerPathForClient maps a client-facing path to the owner's Nextcloud path. func (b *PublicShareBinding) OwnerPathForClient(clientPath, displayName string) string { if b == nil { return NormalizeClientPath(clientPath) } src := b.ClientSourcePath(clientPath, displayName) rel := strings.TrimPrefix(src, "/") sharePath := NormalizeClientPath(b.SharePath) if b.IsSingleFile() { dir := path.Dir(strings.TrimPrefix(sharePath, "/")) if dir == "." || dir == "" { return NormalizeClientPath("/" + rel) } return NormalizeClientPath(path.Join("/", dir, rel)) } if sharePath == "/" || sharePath == "" { return NormalizeClientPath("/" + rel) } return NormalizeClientPath(path.Join(sharePath, rel)) } func normalizeOCSSharePath(p string) string { p = strings.TrimSpace(p) if p == "" { return "/" } if !strings.HasPrefix(p, "/") { p = "/" + p } return NormalizeClientPath(p) } // ResolvePublicShareBinding resolves owner + OCS share metadata for a public token. func (c *Client) ResolvePublicShareBinding(ctx context.Context, token, password string) (*PublicShareBinding, error) { token = strings.TrimSpace(token) if token == "" { return nil, ErrInvalidPublicShare } ownerID, err := c.getPublicShareOwnerID(ctx, token, password) if err != nil { return nil, err } ownerID = strings.TrimSpace(ownerID) if ownerID == "" { return nil, fmt.Errorf("public share owner not found") } shares, err := c.ListShares(ctx, ownerID, "") if err == nil { for _, sh := range shares { if strings.TrimSpace(sh.Token) != token { continue } itemType := strings.TrimSpace(sh.ItemType) if itemType == "" { itemType = "folder" } return &PublicShareBinding{ Token: token, OwnerID: ownerID, SharePath: normalizeOCSSharePath(sh.Path), ItemType: itemType, }, nil } } view, err := c.GetPublicShare(ctx, token, "/", password) if err != nil { return nil, fmt.Errorf("public share token not found: %w", err) } itemType := strings.TrimSpace(view.ItemType) if itemType == "" { itemType = "folder" } sharePath := "/" if itemType == "file" && strings.TrimSpace(view.Name) != "" { sharePath = normalizeOCSSharePath("/" + view.Name) } return &PublicShareBinding{ Token: token, OwnerID: ownerID, SharePath: sharePath, ItemType: itemType, }, nil }