Skip to content

Commit

Permalink
imapclient: add helpers to match FETCH response sections
Browse files Browse the repository at this point in the history
  • Loading branch information
emersion committed Aug 24, 2024
1 parent 1299a86 commit 24c2c59
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 16 deletions.
21 changes: 9 additions & 12 deletions imapclient/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,19 @@ func ExampleClient_Fetch() {
var c *imapclient.Client

seqSet := imap.SeqSetNum(1)
bodySection := &imap.FetchItemBodySection{Specifier: imap.PartSpecifierHeader}
fetchOptions := &imap.FetchOptions{
Flags: true,
Envelope: true,
BodySection: []*imap.FetchItemBodySection{
{Specifier: imap.PartSpecifierHeader},
},
Flags: true,
Envelope: true,
BodySection: []*imap.FetchItemBodySection{bodySection},
}
messages, err := c.Fetch(seqSet, fetchOptions).Collect()
if err != nil {
log.Fatalf("FETCH command failed: %v", err)
}

msg := messages[0]
var header []byte
for _, buf := range msg.BodySection {
header = buf
break
}
header := msg.FindBodySection(bodySection)

log.Printf("Flags: %v", msg.Flags)
log.Printf("Subject: %v", msg.Envelope.Subject)
Expand All @@ -173,9 +168,10 @@ func ExampleClient_Fetch_streamBody() {
var c *imapclient.Client

seqSet := imap.SeqSetNum(1)
bodySection := &imap.FetchItemBodySection{}
fetchOptions := &imap.FetchOptions{
UID: true,
BodySection: []*imap.FetchItemBodySection{{}},
BodySection: []*imap.FetchItemBodySection{bodySection},
}
fetchCmd := c.Fetch(seqSet, fetchOptions)
defer fetchCmd.Close()
Expand Down Expand Up @@ -215,8 +211,9 @@ func ExampleClient_Fetch_parseBody() {

// Send a FETCH command to fetch the message body
seqSet := imap.SeqSetNum(1)
bodySection := &imap.FetchItemBodySection{}
fetchOptions := &imap.FetchOptions{
BodySection: []*imap.FetchItemBodySection{{}},
BodySection: []*imap.FetchItemBodySection{bodySection},
}
fetchCmd := c.Fetch(seqSet, fetchOptions)
defer fetchCmd.Close()
Expand Down
118 changes: 114 additions & 4 deletions imapclient/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,61 @@ func (cmd *FetchCommand) Collect() ([]*FetchMessageBuffer, error) {
return l, cmd.Close()
}

func matchFetchItemBodySection(cmd, resp *imap.FetchItemBodySection) bool {
if cmd.Specifier != resp.Specifier {
return false
}

if !intSliceEqual(cmd.Part, resp.Part) {
return false
}
if !stringSliceEqualFold(cmd.HeaderFields, resp.HeaderFields) {
return false
}
if !stringSliceEqualFold(cmd.HeaderFieldsNot, resp.HeaderFieldsNot) {
return false
}

if (cmd.Partial == nil) != (resp.Partial == nil) {
return false
}
if cmd.Partial != nil && cmd.Partial.Offset != resp.Partial.Offset {
return false
}

// Ignore Partial.Size and Peek: these are not echoed back by the server
return true
}

func matchFetchItemBinarySection(cmd, resp *imap.FetchItemBinarySection) bool {
// Ignore Partial and Peek: these are not echoed back by the server
return intSliceEqual(cmd.Part, resp.Part)
}

func intSliceEqual(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

func stringSliceEqualFold(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !strings.EqualFold(a[i], b[i]) {
return false
}
}
return true
}

// FetchMessageData contains a message's FETCH data.
type FetchMessageData struct {
SeqNum uint32
Expand Down Expand Up @@ -320,8 +375,14 @@ type FetchItemDataBodySection struct {

func (FetchItemDataBodySection) fetchItemData() {}

func (item FetchItemDataBodySection) discard() {
io.Copy(io.Discard, item.Literal)
func (dataItem FetchItemDataBodySection) discard() {
io.Copy(io.Discard, dataItem.Literal)
}

// MatchCommand checks whether a section returned by the server in a response
// is compatible with a section requested by the client in a command.
func (dataItem *FetchItemDataBodySection) MatchCommand(item *imap.FetchItemBodySection) bool {
return matchFetchItemBodySection(item, dataItem.Section)
}

// FetchItemDataBinarySection holds data returned by FETCH BINARY[].
Expand All @@ -332,8 +393,14 @@ type FetchItemDataBinarySection struct {

func (FetchItemDataBinarySection) fetchItemData() {}

func (item FetchItemDataBinarySection) discard() {
io.Copy(io.Discard, item.Literal)
func (dataItem FetchItemDataBinarySection) discard() {
io.Copy(io.Discard, dataItem.Literal)
}

// MatchCommand checks whether a section returned by the server in a response
// is compatible with a section requested by the client in a command.
func (dataItem *FetchItemDataBinarySection) MatchCommand(item *imap.FetchItemBinarySection) bool {
return matchFetchItemBinarySection(item, dataItem.Section)
}

// FetchItemDataFlags holds data returned by FETCH FLAGS.
Expand Down Expand Up @@ -388,6 +455,13 @@ type FetchItemDataBinarySectionSize struct {

func (FetchItemDataBinarySectionSize) fetchItemData() {}

// MatchCommand checks whether a section size returned by the server in a
// response is compatible with a section size requested by the client in a
// command.
func (data *FetchItemDataBinarySectionSize) MatchCommand(item *imap.FetchItemBinarySectionSize) bool {
return intSliceEqual(item.Part, data.Part)
}

// FetchItemDataModSeq holds data returned by FETCH MODSEQ.
//
// This requires the CONDSTORE extension.
Expand Down Expand Up @@ -456,6 +530,42 @@ func (buf *FetchMessageBuffer) populateItemData(item FetchItemData) error {
return nil
}

// FindBodySection returns the contents of a requested body section.
//
// If the body section is not found, nil is returned.
func (buf *FetchMessageBuffer) FindBodySection(section *imap.FetchItemBodySection) []byte {
for s, b := range buf.BodySection {
if matchFetchItemBodySection(section, s) {
return b
}
}
return nil
}

// FindBinarySection returns the contents of a requested binary section.
//
// If the binary section is not found, nil is returned.
func (buf *FetchMessageBuffer) FindBinarySection(section *imap.FetchItemBinarySection) []byte {
for s, b := range buf.BinarySection {
if matchFetchItemBinarySection(section, s) {
return b
}
}
return nil
}

// FindBinarySectionSize returns a requested binary section size.
//
// If the binary section size is not found, false is returned.
func (buf *FetchMessageBuffer) FindBinarySectionSize(part []int) (uint32, bool) {
for _, s := range buf.BinarySectionSize {
if intSliceEqual(part, s.Part) {
return s.Size, true
}
}
return 0, false
}

func (c *Client) handleFetch(seqNum uint32) error {
dec := c.dec

Expand Down

0 comments on commit 24c2c59

Please sign in to comment.