Ejemplo n.º 1
1
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
}
Ejemplo n.º 2
0
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
}
Ejemplo n.º 3
0
/**
 * 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")
	}
}
Ejemplo n.º 4
0
// 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
}
Ejemplo n.º 5
0
// 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
}
Ejemplo n.º 6
0
/**
 * 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
}
Ejemplo n.º 7
0
/**
 * 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
}