-
Couldn't load subscription status.
- Fork 331
Support for rfc5465: The IMAP NOTIFY Extension #718
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v2
Are you sure you want to change the base?
Conversation
|
v2: removed a very long example from the docs. It's very easy for this to fall out of sync and not really useful. |
|
Tests with dovecot failed. When writing these tests I didn't realise that they also run with another server in CI. I lean towards dropping the third commit— the one which implements the scaffold for NOTIFY in imapmemserver (along with the test which run server+client). Thoughts? |
|
We can also drop the incomplete imapmemserver support and enable the client-server NOTIFY tests only for dovecot. I see the I'll wait for feedback before advancing this this in any way. |
|
v3: added test for how disconnections are handled (both with and without NOTIFY). Fix |
Client–server tests run only with dovecot; the imapmemserver doesn't support NOTIFY.
|
I've re-written the client test to run only with dovecot, which has real NOTIFY support. Those test will be skipped with the imapmemserver, but are a lot more meaningful than before. I've ripped out the scaffold for the imapmemserver's NOTIFY support. Such broken support was useless beyond these tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the patch! Here are a few comments. I've only read the client part for now, not the tests nor server.
| NotifyEventServerMetadataChange NotifyEvent = "ServerMetadataChange" | ||
| ) | ||
|
|
||
| // NotifyMailboxSpec represents a mailbox specifier (rfc5465#section-6) for the NOTIFY command. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
godoc will linkify RFC references when they are written in English: "RFC 5465 section 6"
| type NotifyOptions struct { | ||
| // STATUS indicates that a STATUS response should be sent for new mailboxes. | ||
| // Only valid with Personal, Inboxes, or Subscribed mailbox specs. | ||
| STATUS bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: in Go we generally use TitleCase instead of UPPERCASE, even if the original spec uses UPPERCASE. (See e.g. Get/Post in net/http.)
| default: | ||
| // Unsolicited STATUS response (e.g., from NOTIFY) | ||
| if handler := c.options.unilateralDataHandler().Status; handler != nil { | ||
| handler(data) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we put this in a if cmd == nil check after findPendingCmdFunc, instead of in a default case? (TBH it would be better to panic in the default case.)
| // These handlers are important when using the NOTIFY command , as the server | ||
| // will send unsolicited STATUS, FETCH, and EXPUNGE responses for mailbox | ||
| // events. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we add something about NOTIFY, we should probably add something about IDLE as well.
Typo: space before ,
| // NotifyNone sends a NOTIFY NONE command to disable all notifications. | ||
| func (c *Client) NotifyNone() error { | ||
| _, err := c.Notify(nil) | ||
| return err | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can leave it up to the user to call Notify with nil instead of providing a thin wrapper? The user might be interested in waiting for the server to accept the command, too.
| func encodeNotifyOptions(enc *imapwire.Encoder, options *imap.NotifyOptions) { | ||
| if options == nil || len(options.Items) == 0 { | ||
| // NOTIFY NONE - disable all notifications | ||
| enc.SP().Atom("NONE") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use an early return to avoid indenting too much
| // Validate the item before encoding | ||
| if item.MailboxSpec == "" && len(item.Mailboxes) == 0 { | ||
| // Skip invalid items - this shouldn't happen with properly constructed NotifyOptions | ||
| continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably return an error here?
| // Encode mailbox specification | ||
| if item.MailboxSpec != "" { | ||
| enc.Atom(string(item.MailboxSpec)) | ||
| } else if len(item.Mailboxes) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should never happen per the above. Would be easier to understand that the mailbox spec or list aren't optional if we dropped this if.
| const ( | ||
| // ResponseCodeNotificationOverflow is returned when the server cannot | ||
| // handle the requested notifications (RFC 5465). | ||
| ResponseCodeNotificationOverflow ResponseCode = "NOTIFICATIONOVERFLOW" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to expose this in our public API if it's never going to be returned in a public-facing ResponseCode field (e.g. Error).
| case "NOTIFICATIONOVERFLOW": | ||
| // Server has disabled NOTIFY due to overflow (RFC 5465 section 5.8) | ||
| if cmd := findPendingCmdByType[*NotifyCommand](c); cmd != nil { | ||
| cmd.handleOverflow() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd prefer a new callback in the unilateral data handler over a goroutine here…
This patch series implements support for the NOTIFY extension.
The first commit implements support in imapclient, the main focus of my work. I've been using this on a client to monitor changes with success.
The second commit implements support in imapserver.
The third commit adds a minimal scaffold implementation in imapmemserver. This implementation simply rejects any request (which it technically allows as per the spec). It's not an actually useful implementation, and is mostly there so we can have minimal client-server unit tests. Implementing proper NOTIFY support for imapmemserver likely requires substantial changes which fall beyond the scope of this series.
This is likely easiest to review on a per-commit basis, and about half of the LoC are just unit tests for encoding/decoding.