Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions imapserver/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (c *Conn) availableCaps() []imap.Cap {
imap.CapCreateSpecialUse,
imap.CapLiteralPlus,
imap.CapUnauthenticate,
imap.CapID,
})
}
return caps
Expand Down
3 changes: 3 additions & 0 deletions imapserver/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ func (c *Conn) readCommand(dec *imapwire.Decoder) error {
err = c.handleLogout(dec)
case "CAPABILITY":
err = c.handleCapability(dec)
case "ID":
err = c.handleID(tag, dec)
sendOK = false
case "STARTTLS":
err = c.handleStartTLS(tag, dec)
sendOK = false
Expand Down
155 changes: 155 additions & 0 deletions imapserver/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package imapserver

import (
"fmt"

"github.com/emersion/go-imap/v2"
"github.com/emersion/go-imap/v2/internal/imapwire"
)

func (c *Conn) handleID(tag string, dec *imapwire.Decoder) error {
idData, err := readID(dec)
if err != nil {
return fmt.Errorf("in id: %v", err)
}

if !dec.ExpectCRLF() {
return dec.Err()
}

var serverIDData *imap.IDData
if idSess, ok := c.session.(SessionID); ok {
serverIDData = idSess.ID(idData)
}

enc := newResponseEncoder(c)
enc.Atom("*").SP().Atom("ID")

if serverIDData == nil {
enc.SP().NIL()
} else {
enc.SP().Special('(')
isFirstKey := true
if serverIDData.Name != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "name", serverIDData.Name)
}
if serverIDData.Version != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "version", serverIDData.Version)
}
if serverIDData.OS != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "os", serverIDData.OS)
}
if serverIDData.OSVersion != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "os-version", serverIDData.OSVersion)
}
if serverIDData.Vendor != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "vendor", serverIDData.Vendor)
}
if serverIDData.SupportURL != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "support-url", serverIDData.SupportURL)
}
if serverIDData.Address != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "address", serverIDData.Address)
}
if serverIDData.Date != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "date", serverIDData.Date)
}
if serverIDData.Command != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "command", serverIDData.Command)
}
if serverIDData.Arguments != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "arguments", serverIDData.Arguments)
}
if serverIDData.Environment != "" {
addIDKeyValue(enc.Encoder, &isFirstKey, "environment", serverIDData.Environment)
}
enc.Special(')')
}

err = enc.CRLF()
enc.end()
if err != nil {
return err
}

return c.writeStatusResp(tag, &imap.StatusResponse{
Type: imap.StatusResponseTypeOK,
Text: "ID completed",
})
}

func readID(dec *imapwire.Decoder) (*imap.IDData, error) {
if !dec.ExpectSP() {
return nil, dec.Err()
}

if dec.ExpectNIL() {
return nil, nil
}

data := &imap.IDData{}
currKey := ""
err := dec.ExpectList(func() error {
var keyOrValue string
if !dec.String(&keyOrValue) {
return fmt.Errorf("in id key-val list: %v", dec.Err())
}

if currKey == "" {
currKey = keyOrValue
return nil
}

switch currKey {
case "name":
data.Name = keyOrValue
case "version":
data.Version = keyOrValue
case "os":
data.OS = keyOrValue
case "os-version":
data.OSVersion = keyOrValue
case "vendor":
data.Vendor = keyOrValue
case "support-url":
data.SupportURL = keyOrValue
case "address":
data.Address = keyOrValue
case "date":
data.Date = keyOrValue
case "command":
data.Command = keyOrValue
case "arguments":
data.Arguments = keyOrValue
case "environment":
data.Environment = keyOrValue
default:
// Ignore unknown key
}
currKey = ""

return nil
})

if err != nil {
return nil, err
}

return data, nil
}

func addIDKeyValue(enc *imapwire.Encoder, isFirstKey *bool, key, value string) {
if *isFirstKey {
enc.Quoted(key).SP().Quoted(value)
} else {
enc.SP().Quoted(key).SP().Quoted(value)
}
*isFirstKey = false
}

// SessionID is an interface for sessions that can provide server ID information.
type SessionID interface {
// ID returns server information in response to a client ID command.
// The client's ID information is provided if available.
ID(clientID *imap.IDData) *imap.IDData
}