func getThreads(c *imap.Client) ([]Thread, error) { set, err := imap.NewSeqSet("1:*") cmd, err := imap.Wait(c.Fetch(set, "X-GM-THRID", "UID")) if err != nil { fmt.Println(err) return nil, ErrBadConnection } var result []Thread seen := make(map[string]int) for _, rsp := range cmd.Data { thrid := imap.AsString(rsp.MessageInfo().Attrs["X-GM-THRID"]) uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"]) if i, ok := seen[thrid]; ok { result[i] = append(result[i], uid) } else { result = append(result, Thread{uid}) seen[thrid] = len(result) - 1 } } return result, nil }
func getEmails(client *imap.Client, cmd *imap.Command, markAsRead, delete bool, responses chan Response) { seq := &imap.SeqSet{} msgCount := 0 for _, rsp := range cmd.Data { for _, uid := range rsp.SearchResults() { msgCount++ seq.AddNum(uid) } } // nothing to request?! why you even callin me, foolio? if seq.Empty() { return } fCmd, err := imap.Wait(client.UIDFetch(seq, "INTERNALDATE", "BODY[]", "UID", "RFC822.HEADER")) if err != nil { responses <- Response{Err: fmt.Errorf("unable to perform uid fetch: %s", err)} return } var email Email for _, msgData := range fCmd.Data { msgFields := msgData.MessageInfo().Attrs // make sure is a legit response before we attempt to parse it // deal with unsolicited FETCH responses containing only flags // I'm lookin' at YOU, Gmail! // http://mailman13.u.washington.edu/pipermail/imap-protocol/2014-October/002355.html // http://stackoverflow.com/questions/26262472/gmail-imap-is-sometimes-returning-bad-results-for-fetch if _, ok := msgFields["RFC822.HEADER"]; !ok { continue } email, err = newEmail(msgFields) if err != nil { responses <- Response{Err: fmt.Errorf("unable to parse email: %s", err)} return } responses <- Response{Email: email} if !markAsRead { err = removeSeen(client, imap.AsNumber(msgFields["UID"])) if err != nil { responses <- Response{Err: fmt.Errorf("unable to remove seen flag: %s", err)} return } } if delete { err = deleteEmail(client, imap.AsNumber(msgFields["UID"])) if err != nil { responses <- Response{Err: fmt.Errorf("unable to delete email: %s", err)} return } } } return }
/** * This method moves the mail associated with the given 'UID', from the folder where it currently * resides, into the "toFolder" folder and sets its "Deleted" flag. * After this operation, the following post condition holds, if a mail with the given UID existed * in the original folder: * - The mail in the original folder has its "Deleted" flag set * - The folder 'newFolder' now contains a new mail with the Header and Content of the original * mail (but with a new UID) * * ATTENTION: * The original mail is NOT deleted from the folder where it resided (only its deleted flag is set). * The deletion operation will happen when the IMAP connection is closed, or the EXPUNGE operation * is called. * * @param uid The UID of the mail to be moved * @param folder The folder in which the current mail resides (its source location) * @param toFolder The folder the mail should be moved to (the target folder) * @return Returns the UID of the newly created mail in the target folder or an error, if something * went wrong. */ func (mc *MailCon) moveMail_internal(uid, folder, toFolder string) (uint32, error) { var targetMbox string = mc.mailbox // 1) First check if we need to select a specific folder in the mailbox or if it is root if err := mc.selectFolder(folder, true); err != nil { return 0, err } // 2) Assign necessary variables and initiate IMAP Copy process set, _ := imap.NewSeqSet(uid) if len(toFolder) > 0 && toFolder != "/" { targetMbox = fmt.Sprintf("%s%s%s", mc.mailbox, mc.delim, toFolder) } cmd, err := mc.client.UIDCopy(set, targetMbox) var resp *imap.Response if resp, err = cmd.Result(imap.OK); err != nil { return 0, fmt.Errorf("[watney] ERROR waiting for result of copy command\n\t%s\n", err.Error()) } // 3) Execute the copy process to copy the mail internally to the new folder if _, err := mc.waitFor(mc.client.UIDStore(set, "+FLAGS", SerializeFlags(&Flags{Deleted: true}))); err != nil { return 0, fmt.Errorf("[watney] ERROR waiting for result of update flags command\n\t%s\n", err.Error()) } // 4) Check if the copy worked, and if so, return the new UID and no error // The Response is an 'COPYUID' with the fields: // [0] COPYUID:string | [1] internaldate:long64 | [2] Orig-UID:uint32 | [3] New-UID:uint32 // The Orig-UID resambles the given UID for the original mail // The New-UID is the UID of the new mail stored in the 'toFolder' if len(resp.Fields) == 4 { return imap.AsNumber(resp.Fields[3]), nil } else { return 0, errors.New("[watney] WARNING: Copy completed without doing anyting\n") } }
// getEmails will fetch the full bodies of all emails listed in the given command. func getEmails(client *imap.Client, cmd *imap.Command) ([]map[string]interface{}, error) { var emails []map[string]interface{} seq := new(imap.SeqSet) for _, rsp := range cmd.Data { for _, uid := range rsp.SearchResults() { seq.AddNum(uid) } } if seq.Empty() { return emails, nil } fCmd, err := imap.Wait(client.UIDFetch(seq, "INTERNALDATE", "BODY[]", "UID", "RFC822.HEADER")) if err != nil { return emails, err } var email map[string]interface{} for _, msgData := range fCmd.Data { msgFields := msgData.MessageInfo().Attrs email, err = newEmailMessage(msgFields) if err != nil { return emails, err } emails = append(emails, email) // mark message as read fSeq := new(imap.SeqSet) fSeq.AddNum(imap.AsNumber(msgFields["UID"])) _, err = imap.Wait(client.UIDStore(fSeq, "+FLAGS", "\\SEEN")) if err != nil { return emails, err } } return emails, nil }
// handleMessage processes one message, invokes the callback and deletes it on // success. func (w *IMAPSource) handleMessage(rsp *imap.Response) error { msgInfo := rsp.MessageInfo() err := w.invokeMessageCallback(msgInfo) if err != nil { return err } uid := imap.AsNumber(msgInfo.Attrs["UID"]) logger.Debugf("internally marking message uid=%d for deletion", uid) w.deletionSet.AddNum(uid) return err }
/** * Response: * 25 EXISTS | 2 | 25 * new Data update: * 25 EXISTS * This is all the fields we get: %!s(uint32=25) * This is all the fields we get: EXISTS * Response: * 1 RECENT | 2 | 1 * new Data update: * 1 RECENT * This is all the fields we get: %!s(uint32=1) * This is all the fields we get: RECENT * * Usually, a new mail update from the server splits into 2 responses: 1 EXIST and 1 RECENT * The EXIST command provides the server UID of the new message received and the RECENT command * tells the client how many new messages have been recently received. * * @return Array of sequence numbers for all newly received mails */ func (mc *MailCon) CheckNewMails() ([]uint32, error) { mc.mutex.Lock() defer mc.mutex.Unlock() var ( recentMails uint32 lastSeqNumber uint32 newMailSeqNumbers = make([]uint32, 0) err error = nil ) if mc.client.Data != nil && len(mc.client.Data) > 0 { for _, resp := range mc.client.Data { fmt.Printf("Response: %s | %d | %d\n", resp.String(), resp.Type, resp.Value()) if resp.Type == imap.Data { fmt.Printf("new Data update: %s | Label: %s\n", resp.String(), resp.Label) f := resp.Fields if len(f) > 1 { // The response is either an EXIST or a RECENT // Fields[0] | Fields[1] // ---------------------------|------------------------------- // last sequence number : int | EXIST // number of new mails : int | RECENT switch n := imap.AsNumber(f[0]); strings.ToUpper(imap.AsAtom(f[1])) { case "RECENT": recentMails = n case "EXISTS": lastSeqNumber = n } } else { err = errors.New(fmt.Sprintf("Got a data update message with less than 2 "+ "fields: %s", resp.Fields)) fmt.Printf("[watney]: ERROR: %s\n", err.Error()) } } else { err = errors.New(fmt.Sprintf("Unhandled response in message queue, while "+ "checking for new mails: \n\t%s", resp.String())) } } } // fmt.Printf("Received info is: %b, %s\n", recentMsg, newMsgUIDs) // Empty the response message queue mc.client.Data = nil // Compute sequence numbers for all new mails var i uint32 for i = 0; i < recentMails; i++ { newMailSeqNumbers = append(newMailSeqNumbers, lastSeqNumber-i) } return newMailSeqNumbers, err }
/** * Creates a new mail on the IMAP server with the given header information, flags and content * (body). * ATTENTION: DOES NOT LOCK THE IMAP CONNECTION! => Has to be wrapped into a mutex lock method */ func (mc *MailCon) createMailInFolder_internal(h *Header, f *Flags, content string) (uid uint32, err error) { var ( // Create the msg: // Header info + empty line + content + empty line msg string = strings.Join([]string{SerializeHeader(h), "", content, ""}, "\r\n") lit imap.Literal = imap.NewLiteral([]byte(msg)) mbox string = fmt.Sprintf("%s%s%s", mc.mailbox, mc.delim, h.Folder) cmd *imap.Command resp *imap.Response ) // 1) Execute the actual append mail command if cmd, err = mc.client.Append(mbox, imap.AsFlagSet(SerializeFlags(f)), &h.Date, lit); err != nil { return 0, err } if resp, err = cmd.Result(imap.OK); err != nil { return 0, fmt.Errorf("[watney] ERROR waiting for result of append command\n\t%s\n", err.Error()) } // 2) Process the server response and extract the message UID of the previously added mail // The Response is an 'APPENDUID' with the fields: // [0] APPENDUID:string | [1] internaldate:long64 | [2] UID:uint32 return imap.AsNumber(resp.Fields[2]), err }