mirror of
https://github.com/mjl-/mox.git
synced 2025-07-13 12:14:37 +03:00
imap metadata extension: allow keys in the /shared/ namespace too
not just /private. /shared/ is the more commonly implemented namespace, because it is easier te implement: you don't need per-user/account storage of metadata. i initially approached it from the other direction: we don't have a mechanism to share metadata with other accounts, so everything is private, and i assumed that would be what a user would prefer. but email clients make the decisions, and they'll likely try the /shared/ namespace.
This commit is contained in:
@ -181,8 +181,8 @@ func (c *conn) cmdGetmetadata(tag, cmd string, p *parser) {
|
||||
|
||||
// Set metadata annotation, per mailbox or globally.
|
||||
//
|
||||
// We only implement private annotations, not shared annotations. We don't
|
||||
// currently have a mechanism for determining if the user should have access.
|
||||
// We allow both /private/* and /shared/*, we store them in the same way since we
|
||||
// don't have ACL extension support yet or another mechanism for access control.
|
||||
//
|
||||
// State: Authenticated and selected.
|
||||
func (c *conn) cmdSetmetadata(tag, cmd string, p *parser) {
|
||||
@ -211,20 +211,22 @@ func (c *conn) cmdSetmetadata(tag, cmd string, p *parser) {
|
||||
|
||||
// Additional checks on entry names.
|
||||
for _, a := range l {
|
||||
// We only allow /private/* entry names, so check early and fail if we see anything
|
||||
// else (the only other option is /shared/* at this moment).
|
||||
// ../rfc/5464:217
|
||||
if !strings.HasPrefix(a.Key, "/private/") {
|
||||
if !strings.HasPrefix(a.Key, "/private/") && !strings.HasPrefix(a.Key, "/shared/") {
|
||||
// ../rfc/5464:346
|
||||
xuserErrorf("only /private/* entry names allowed")
|
||||
xuserErrorf("only /private/* and /shared/* entry names allowed")
|
||||
}
|
||||
|
||||
// We also enforce that /private/vendor/ is followed by at least 2 elements.
|
||||
// ../rfc/5464:234
|
||||
if a.Key == "/private/vendor" || strings.HasPrefix(a.Key, "/private/vendor/") {
|
||||
switch {
|
||||
case a.Key == "/private/vendor",
|
||||
strings.HasPrefix(a.Key, "/private/vendor/"),
|
||||
a.Key == "/shared/vendor", strings.HasPrefix(a.Key, "/shared/vendor/"):
|
||||
|
||||
t := strings.SplitN(a.Key[1:], "/", 4)
|
||||
if len(t) < 4 {
|
||||
xuserErrorf("entry names starting with /private/vendor must have at least 4 components")
|
||||
xuserErrorf("entry names starting with /private/vendor or /shared/vendor must have at least 4 components")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,16 +31,18 @@ func TestMetadata(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
tc.transactf("ok", `setmetadata Inbox (/shared/comment "share")`)
|
||||
|
||||
tc.transactf("ok", `getmetadata inbox (/private/comment /private/unknown /shared/comment)`)
|
||||
tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
|
||||
Mailbox: "Inbox",
|
||||
Annotations: []imapclient.Annotation{
|
||||
{Key: "/private/comment", IsString: true, Value: []byte("mailbox value")},
|
||||
{Key: "/shared/comment", IsString: true, Value: []byte("share")},
|
||||
},
|
||||
})
|
||||
|
||||
tc.transactf("no", `setmetadata doesnotexist (/private/comment "test")`) // Bad mailbox.
|
||||
tc.transactf("no", `setmetadata Inbox (/shared/comment "")`) // /shared/ not implemented.
|
||||
tc.transactf("no", `setmetadata Inbox (/badprefix/comment "")`)
|
||||
tc.transactf("no", `setmetadata Inbox (/private/vendor "")`) // /*/vendor must have more components.
|
||||
tc.transactf("no", `setmetadata Inbox (/private/vendor/stillbad "")`) // /*/vendor must have more components.
|
||||
@ -131,7 +133,7 @@ func TestMetadata(t *testing.T) {
|
||||
},
|
||||
})
|
||||
// Same as previous, but ask for everything below /.
|
||||
tc.transactf("ok", `getmetadata (depth infinity) inbox (/)`)
|
||||
tc.transactf("ok", `getmetadata (depth infinity) inbox ("")`)
|
||||
tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
|
||||
Mailbox: "Inbox",
|
||||
Annotations: []imapclient.Annotation{
|
||||
@ -142,6 +144,7 @@ func TestMetadata(t *testing.T) {
|
||||
{Key: "/private/another", IsString: true, Value: []byte("longer")},
|
||||
{Key: "/private/comment", IsString: false, Value: []byte("test")},
|
||||
{Key: "/private/vendor/a/b", IsString: true, Value: []byte("")},
|
||||
{Key: "/shared/comment", IsString: true, Value: []byte("share")},
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -229,8 +229,8 @@ type Annotation struct {
|
||||
// Can be zero, indicates global (per-account) annotation.
|
||||
MailboxID int64 `bstore:"ref Mailbox,unique MailboxID+Key"`
|
||||
|
||||
// "Entry name", always starts with "/private/". Stored lower-case, comparisons
|
||||
// must be done case-insensitively.
|
||||
// "Entry name", always starts with "/private/" or "/shared/". Stored lower-case,
|
||||
// comparisons must be done case-insensitively.
|
||||
Key string `bstore:"nonzero"`
|
||||
|
||||
IsString bool // If true, the value is a string instead of bytes.
|
||||
|
Reference in New Issue
Block a user