Skip to content

Commit a0d5e18

Browse files
foxcppemersion
authored andcommitted
Merge support for the UNSELECT extension
1 parent 7b7dd37 commit a0d5e18

File tree

9 files changed

+101
-6
lines changed

9 files changed

+101
-6
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ includes:
137137
* [SASL-IR](https://tools.ietf.org/html/rfc4959)
138138
* [SPECIAL-USE](https://tools.ietf.org/html/rfc6154)
139139
* [CHILDREN](https://tools.ietf.org/html/rfc3348)
140+
* [UNSELECT](https://tools.ietf.org/html/rfc3691)
140141

141142
Support for other extensions is provided via separate packages. See below.
142143

@@ -158,7 +159,6 @@ to learn how to use them.
158159
* [NAMESPACE](https://github.com/foxcpp/go-imap-namespace)
159160
* [QUOTA](https://github.com/emersion/go-imap-quota)
160161
* [SORT and THREAD](https://github.com/emersion/go-imap-sortthread)
161-
* [UNSELECT](https://github.com/emersion/go-imap-unselect)
162162
* [UIDPLUS](https://github.com/emersion/go-imap-uidplus)
163163

164164
### Server backends

client/client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (c *serverConn) WriteString(s string) (n int, err error) {
4949
}
5050

5151
func newTestClient(t *testing.T) (c *Client, s *serverConn) {
52-
return newTestClientWithGreeting(t, "* OK [CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN] Server ready.\r\n")
52+
return newTestClientWithGreeting(t, "* OK [CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN UNSELECT] Server ready.\r\n")
5353
}
5454

5555
func newTestClientWithGreeting(t *testing.T, greeting string) (c *Client, s *serverConn) {

client/cmd_any.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (c *Client) Support(cap string) (bool, error) {
4949
c.locker.Lock()
5050
supported := c.caps[cap]
5151
c.locker.Unlock()
52+
5253
return supported, nil
5354
}
5455

client/cmd_selected.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ import (
88
"github.com/emersion/go-imap/responses"
99
)
1010

11-
// ErrNoMailboxSelected is returned if a command that requires a mailbox to be
12-
// selected is called when there isn't.
13-
var ErrNoMailboxSelected = errors.New("No mailbox selected")
11+
var (
12+
// ErrNoMailboxSelected is returned if a command that requires a mailbox to be
13+
// selected is called when there isn't.
14+
ErrNoMailboxSelected = errors.New("No mailbox selected")
15+
16+
// ErrExtensionUnsupported is returned if a command uses a extension that
17+
// is not supported by the server.
18+
ErrExtensionUnsupported = errors.New("The required extension is not supported by the server")
19+
)
1420

1521
// Check requests a checkpoint of the currently selected mailbox. A checkpoint
1622
// refers to any implementation-dependent housekeeping associated with the
@@ -265,3 +271,30 @@ func (c *Client) Copy(seqset *imap.SeqSet, dest string) error {
265271
func (c *Client) UidCopy(seqset *imap.SeqSet, dest string) error {
266272
return c.copy(true, seqset, dest)
267273
}
274+
275+
// Unselect frees server's resources associated with the selected mailbox and
276+
// returns the server to the authenticated state. This command performs the same
277+
// actions as Close, except that no messages are permanently removed from the
278+
// currently selected mailbox.
279+
//
280+
// If client does not support the UNSELECT extension, ErrExtensionUnsupported
281+
// is returned.
282+
func (c *Client) Unselect() error {
283+
if ok, err := c.Support("UNSELECT"); !ok || err != nil {
284+
return ErrExtensionUnsupported
285+
}
286+
287+
if c.State() != imap.SelectedState {
288+
return ErrNoMailboxSelected
289+
}
290+
291+
cmd := &commands.Unselect{}
292+
if status, err := c.Execute(cmd, nil); err != nil {
293+
return err
294+
} else if err := status.Err(); err != nil {
295+
return err
296+
}
297+
298+
c.SetState(imap.AuthenticatedState, nil)
299+
return nil
300+
}

client/cmd_selected_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,3 +509,30 @@ func TestClient_Copy_Uid(t *testing.T) {
509509
t.Fatalf("c.UidCopy() = %v", err)
510510
}
511511
}
512+
513+
func TestClient_Unselect(t *testing.T) {
514+
c, s := newTestClient(t)
515+
defer s.Close()
516+
517+
setClientState(c, imap.SelectedState, nil)
518+
519+
done := make(chan error, 1)
520+
go func() {
521+
done <- c.Unselect()
522+
}()
523+
524+
tag, cmd := s.ScanCmd()
525+
if cmd != "UNSELECT" {
526+
t.Fatalf("client sent command %v, want %v", cmd, "UNSELECT")
527+
}
528+
529+
s.WriteString(tag + " OK UNSELECT completed\r\n")
530+
531+
if err := <-done; err != nil {
532+
t.Fatalf("c.Unselect() = %v", err)
533+
}
534+
535+
if c.State() != imap.AuthenticatedState {
536+
t.Fatal("Client is not Authenticated after UNSELECT")
537+
}
538+
}

commands/unselect.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package commands
2+
3+
import (
4+
"github.com/emersion/go-imap"
5+
)
6+
7+
// An UNSELECT command.
8+
// See RFC 3691 section 2.
9+
type Unselect struct{}
10+
11+
func (cmd *Unselect) Command() *imap.Command {
12+
return &imap.Command{Name: "UNSELECT"}
13+
}
14+
15+
func (cmd *Unselect) Parse(fields []interface{}) error {
16+
return nil
17+
}

server/cmd_auth.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,18 @@ func (cmd *Append) Handle(conn Conn) error {
275275

276276
return nil
277277
}
278+
279+
type Unselect struct {
280+
commands.Unselect
281+
}
282+
283+
func (cmd *Unselect) Handle(conn Conn) error {
284+
ctx := conn.Context()
285+
if ctx.Mailbox == nil {
286+
return ErrNoMailboxSelected
287+
}
288+
289+
ctx.Mailbox = nil
290+
ctx.MailboxReadOnly = false
291+
return nil
292+
}

server/conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func (c *conn) Close() error {
163163
}
164164

165165
func (c *conn) Capabilities() []string {
166-
caps := []string{"IMAP4rev1", "LITERAL+", "SASL-IR", "CHILDREN"}
166+
caps := []string{"IMAP4rev1", "LITERAL+", "SASL-IR", "CHILDREN", "UNSELECT"}
167167

168168
if c.ctx.State == imap.NotAuthenticatedState {
169169
if !c.IsTLS() && c.s.TLSConfig != nil {

server/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ func New(bkd backend.Backend) *Server {
185185
"STORE": func() Handler { return &Store{} },
186186
"COPY": func() Handler { return &Copy{} },
187187
"UID": func() Handler { return &Uid{} },
188+
189+
"UNSELECT": func() Handler { return &Unselect{} },
188190
}
189191

190192
return s

0 commit comments

Comments
 (0)