mirror of
https://github.com/mjl-/mox.git
synced 2025-07-13 17:34: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.
|
// Set metadata annotation, per mailbox or globally.
|
||||||
//
|
//
|
||||||
// We only implement private annotations, not shared annotations. We don't
|
// We allow both /private/* and /shared/*, we store them in the same way since we
|
||||||
// currently have a mechanism for determining if the user should have access.
|
// don't have ACL extension support yet or another mechanism for access control.
|
||||||
//
|
//
|
||||||
// State: Authenticated and selected.
|
// State: Authenticated and selected.
|
||||||
func (c *conn) cmdSetmetadata(tag, cmd string, p *parser) {
|
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.
|
// Additional checks on entry names.
|
||||||
for _, a := range l {
|
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
|
// ../rfc/5464:217
|
||||||
if !strings.HasPrefix(a.Key, "/private/") {
|
if !strings.HasPrefix(a.Key, "/private/") && !strings.HasPrefix(a.Key, "/shared/") {
|
||||||
// ../rfc/5464:346
|
// ../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.
|
// We also enforce that /private/vendor/ is followed by at least 2 elements.
|
||||||
// ../rfc/5464:234
|
// ../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)
|
t := strings.SplitN(a.Key[1:], "/", 4)
|
||||||
if len(t) < 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.transactf("ok", `getmetadata inbox (/private/comment /private/unknown /shared/comment)`)
|
||||||
tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
|
tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
|
||||||
Mailbox: "Inbox",
|
Mailbox: "Inbox",
|
||||||
Annotations: []imapclient.Annotation{
|
Annotations: []imapclient.Annotation{
|
||||||
{Key: "/private/comment", IsString: true, Value: []byte("mailbox value")},
|
{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 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 (/badprefix/comment "")`)
|
||||||
tc.transactf("no", `setmetadata Inbox (/private/vendor "")`) // /*/vendor must have more components.
|
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.
|
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 /.
|
// 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{
|
tc.xuntagged(imapclient.UntaggedMetadataAnnotations{
|
||||||
Mailbox: "Inbox",
|
Mailbox: "Inbox",
|
||||||
Annotations: []imapclient.Annotation{
|
Annotations: []imapclient.Annotation{
|
||||||
@ -142,6 +144,7 @@ func TestMetadata(t *testing.T) {
|
|||||||
{Key: "/private/another", IsString: true, Value: []byte("longer")},
|
{Key: "/private/another", IsString: true, Value: []byte("longer")},
|
||||||
{Key: "/private/comment", IsString: false, Value: []byte("test")},
|
{Key: "/private/comment", IsString: false, Value: []byte("test")},
|
||||||
{Key: "/private/vendor/a/b", IsString: true, Value: []byte("")},
|
{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.
|
// Can be zero, indicates global (per-account) annotation.
|
||||||
MailboxID int64 `bstore:"ref Mailbox,unique MailboxID+Key"`
|
MailboxID int64 `bstore:"ref Mailbox,unique MailboxID+Key"`
|
||||||
|
|
||||||
// "Entry name", always starts with "/private/". Stored lower-case, comparisons
|
// "Entry name", always starts with "/private/" or "/shared/". Stored lower-case,
|
||||||
// must be done case-insensitively.
|
// comparisons must be done case-insensitively.
|
||||||
Key string `bstore:"nonzero"`
|
Key string `bstore:"nonzero"`
|
||||||
|
|
||||||
IsString bool // If true, the value is a string instead of bytes.
|
IsString bool // If true, the value is a string instead of bytes.
|
||||||
|
Reference in New Issue
Block a user