func (g *Gmail) saveHeaderFields(headerValue string) { emails, err := mail.ParseAddressList(headerValue) if err != nil { log.Println("Unable to parse:", headerValue) } else { for _, v := range emails { name := v.Name email := v.Address if !g.isKnownEmail(email) { err := errors.New("") err = g.db.SetContact(name, email) if err != nil { log.Println("Unable to save email:", email) } } else { log.Println("Known email. Ignoring:", email) } } } }
// AddressList returns a mail.Address slice with RFC 2047 encoded encoded names. func (m *MIMEBody) AddressList(key string) ([]*mail.Address, error) { isAddrHeader := false for _, hkey := range AddressHeaders { if strings.ToLower(hkey) == strings.ToLower(key) { isAddrHeader = true break } } if !isAddrHeader { return nil, fmt.Errorf("%s is not address header", key) } str := DecodeToUTF8Base64Header(m.header.Get(key)) if str == "" { return nil, mail.ErrHeaderNotPresent } // These statements are handy for debugging ParseAddressList errors // fmt.Println("in: ", m.header.Get(key)) // fmt.Println("out: ", str) ret, err := mail.ParseAddressList(str) if err != nil { return nil, err } return ret, nil }
func getMultipleAddress(raw string) []*mail.Address { addr, err := mail.ParseAddressList(raw) if err != nil { return nil } return addr }
func TestAddBccRecipients(t *testing.T) { m := NewMail() emails, _ := mail.ParseAddressList("Joe <*****@*****.**>, Doe <*****@*****.**>") m.AddBccRecipients(emails) if len(m.Bcc) != 2 { t.Errorf("AddBccRecipients should append to SGMail.Bcc") } }
func parseRecipients(input *string) []*mail.Address { recipients, err := mail.ParseAddressList(*input) if err != nil { return []*mail.Address{} } log.Println("Recipients are", recipients) return recipients }
func buildCCs(m *Message) []*mail.Address { cc, err := mail.ParseAddressList(m.CCs) if err != nil { cc = []*mail.Address{} } to, err := mail.ParseAddressList(m.To) if err != nil { to = []*mail.Address{} } all := append(to, cc...) ret := make([]*mail.Address, 0) for _, e := range all { if !isMe(e) { ret = append(ret, e) } } return ret }
func (m *Mailer) String() (string, error) { message, header := "", make(map[string]string) headers := map[string][]string{ "From": []string{m.Mail.From}, "To": m.Mail.To, "Cc": m.Mail.Cc, "Bcc": m.Mail.Bcc, } for key, addresses := range headers { formated_addresses := []string{} for _, address := range addresses { parsed_addresses, err := mail.ParseAddressList(address) if err == nil { for _, parsed_address := range parsed_addresses { if len(parsed_address.Name) > 0 { formated_addresses = append(formated_addresses, fmt.Sprintf("%v <%v>", parsed_address.Name, parsed_address.Address)) } else { formated_addresses = append(formated_addresses, fmt.Sprintf("%v", parsed_address.Address)) } } } } header[key] = strings.Join(formated_addresses, ", ") } header["Subject"] = m.Mail.Subject header["MIME-Version"] = "1.0" header["Content-Type"] = m.contentType() header["Content-Transfer-Encoding"] = m.contentTransferEncoding() for k, v := range header { if len(v) > 0 { message += fmt.Sprintf("%s: %s\r\n", k, v) } } if len(m.Mail.Bodys) == 0 { m.Body("") } for _, body := range m.Mail.Bodys { message += m.crlfBoundary() message += body.Encode() + "\r\n" } for _, attachment := range m.Mail.Attachments { message += m.crlfBoundary() message += attachment.Encode() + "\r\n" } message += m.endBoundary() return message, nil }
func TestAddRecipients(t *testing.T) { m := NewMail() emails, _ := mail.ParseAddressList("Joe <*****@*****.**>, Doe <*****@*****.**>") m.AddRecipients(emails) switch { case len(m.To) != 2: t.Errorf("AddRecipients should append to SGMail.To") case len(m.ToName) != 2: t.Errorf("AddRecipients should append to SGMail.ToName if a valid email is supplied") } }
// Given a string like "Cathal Garvey <*****@*****.**>, Stephen Barr <*****@*****.**>" // return []string{"Cathal Garvey <*****@*****.**>", "Stephen Barr <*****@*****.**>"} func parseMultiExpressiveEmails(entry string) ([]string, error) { out := make([]string, 0) parsed, err := mail.ParseAddressList(entry) if err != nil { return nil, err } for _, m := range parsed { out = append(out, m.String()) } return out, nil }
// ParseAddressList splits a comma separated list of addresses // into multiple email addresses. The returned addresses can be // used to call Send(). func ParseAddressList(s string) ([]string, error) { addrs, err := mail.ParseAddressList(s) if err != nil { return nil, err } return generic.Map(addrs, func(addr *mail.Address) string { if addr.Name != "" { return fmt.Sprintf("%s <%s>", addr.Name, addr.Address) } return addr.Address }).([]string), nil }
func ParseAddressList() { const list = `[email protected], Alice <*****@*****.**>, "Bob Uncle" <*****@*****.**>, "Yang-Tan, Eve" <*****@*****.**>` emails, err := mail.ParseAddressList(list) if err != nil { log.Fatal(err) } for _, v := range emails { fmt.Println(v.Name, "=>", v.Address) } }
// Create a new email message, returning any reasons for failure. func NewMessageDebug(from, to, subject, body string) (*Message, error) { fromAddress, err := mail.ParseAddress(from) if err != nil { return nil, errors.New("Invalid from address; " + err.Error()) } toAddresses, err := mail.ParseAddressList(to) if err != nil { return nil, errors.New("Invalid to address(es); " + err.Error()) } return &Message{fromAddress, &AddressList{toAddresses}, subject, body}, nil }
func validateEmail(ctx context.Context, address string, resolver bdns.DNSResolver) (prob *probs.ProblemDetails) { emails, err := mail.ParseAddressList(address) if err != nil { return &probs.ProblemDetails{ Type: probs.InvalidEmailProblem, Detail: unparseableEmailDetail, } } if len(emails) > 1 { return &probs.ProblemDetails{ Type: probs.InvalidEmailProblem, Detail: multipleAddressDetail, } } splitEmail := strings.SplitN(emails[0].Address, "@", -1) domain := strings.ToLower(splitEmail[len(splitEmail)-1]) var resultMX []string var resultA []net.IP var errMX, errA error var wg sync.WaitGroup wg.Add(2) go func() { resultMX, errMX = resolver.LookupMX(ctx, domain) wg.Done() }() go func() { resultA, errA = resolver.LookupHost(ctx, domain) wg.Done() }() wg.Wait() if errMX != nil { prob := bdns.ProblemDetailsFromDNSError(errMX) prob.Type = probs.InvalidEmailProblem return prob } else if len(resultMX) > 0 { return nil } if errA != nil { prob := bdns.ProblemDetailsFromDNSError(errA) prob.Type = probs.InvalidEmailProblem return prob } else if len(resultA) > 0 { return nil } return &probs.ProblemDetails{ Type: probs.InvalidEmailProblem, Detail: emptyDNSResponseDetail, } }
func (w EMailWriter) Write(rs ...models.ScanResult) (err error) { conf := config.Conf to := strings.Join(conf.EMail.To[:], ", ") cc := strings.Join(conf.EMail.Cc[:], ", ") mailAddresses := append(conf.EMail.To, conf.EMail.Cc...) if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil { return fmt.Errorf("Failed to parse email addresses: %s", err) } for _, r := range rs { subject := fmt.Sprintf("%s%s %s", conf.EMail.SubjectPrefix, r.ServerInfo(), r.CveSummary(), ) headers := make(map[string]string) headers["From"] = conf.EMail.From headers["To"] = to headers["Cc"] = cc headers["Subject"] = subject headers["Date"] = time.Now().Format(time.RFC1123Z) headers["Content-Type"] = "text/plain; charset=utf-8" var message string for k, v := range headers { message += fmt.Sprintf("%s: %s\r\n", k, v) } message += "\r\n" + toFullPlainText(r) smtpServer := net.JoinHostPort(conf.EMail.SMTPAddr, conf.EMail.SMTPPort) err = smtp.SendMail( smtpServer, smtp.PlainAuth( "", conf.EMail.User, conf.EMail.Password, conf.EMail.SMTPAddr, ), conf.EMail.From, conf.EMail.To, []byte(message), ) if err != nil { return fmt.Errorf("Failed to send emails: %s", err) } } return nil }
// checkContactEmail checks the specified Contact Email and sets an appropriate erorr message // if there's something wrong with it. // Returns true if email is acceptable (valid or empty). func checkContactEmail(p *page.Params, email string) (ok bool) { if email == "" { return true } if len(email) > 500 { p.ErrorMsg = template.HTML(`<span class="code">Contact email</span> is too long! (cannot be longer than 500 characters)`) return false } if _, err := mail.ParseAddressList(email); err != nil { p.ErrorMsg = template.HTML(`Invalid <span class="code">Contact email</span>!`) return false } return true }
func validateEmail(ctx context.Context, address string, resolver bdns.DNSResolver) (prob *probs.ProblemDetails) { emails, err := mail.ParseAddressList(address) if err != nil { return probs.InvalidEmail(unparseableEmailDetail) } if len(emails) > 1 { return probs.InvalidEmail(multipleAddressDetail) } splitEmail := strings.SplitN(emails[0].Address, "@", -1) domain := strings.ToLower(splitEmail[len(splitEmail)-1]) var resultMX []string var resultA []net.IP var errMX, errA error var wg sync.WaitGroup wg.Add(2) go func() { resultMX, errMX = resolver.LookupMX(ctx, domain) wg.Done() }() go func() { resultA, errA = resolver.LookupHost(ctx, domain) wg.Done() }() wg.Wait() // We treat timeouts as non-failures for best-effort email validation // See: https://github.com/letsencrypt/boulder/issues/2260 if problemIsTimeout(errMX) || problemIsTimeout(errA) { return nil } if errMX != nil { prob := bdns.ProblemDetailsFromDNSError(errMX) prob.Type = probs.InvalidEmailProblem return prob } else if len(resultMX) > 0 { return nil } if errA != nil { prob := bdns.ProblemDetailsFromDNSError(errA) prob.Type = probs.InvalidEmailProblem return prob } else if len(resultA) > 0 { return nil } return probs.InvalidEmail(emptyDNSResponseDetail) }
func ExampleParseAddressList() { const list = "Alice <*****@*****.**>, Bob <*****@*****.**>, Eve <*****@*****.**>" emails, err := mail.ParseAddressList(list) if err != nil { log.Fatal(err) } for _, v := range emails { fmt.Println(v.Name, v.Address) } // Output: // Alice [email protected] // Bob [email protected] // Eve [email protected] }
func contactFormHandler(w http.ResponseWriter, r *http.Request) { if corsUrl != "" { w.Header().Set("Access-Control-Allow-Origin", corsUrl) } w.Header().Set("Content-Type", "application/json; charset=utf-8") fromAddress, _ := mail.ParseAddress(from) toAddresses, _ := mail.ParseAddressList(to) response := make(map[string]string) err := r.ParseForm() if err != nil { response["status"] = "FAILURE" response["message"] = err.Error() jsonStr, _ := json.Marshal(response) fmt.Fprintf(w, string(jsonStr)) return } body := "" for k, v := range r.Form { if k != "body" { body += fmt.Sprintf("%s: %s\r\n", k, v) } } body += "---------------------\r\n" body += r.Form.Get("body") err = sendContactFormEmail(smtpUser, smtpPass, smtpServer, toAddresses, fromAddress, subject, body, ) if err == nil { response["status"] = "SUCCESS" jsonStr, _ := json.Marshal(response) fmt.Fprintf(w, string(jsonStr)) } else { response["status"] = "FAILURE" response["message"] = err.Error() jsonStr, _ := json.Marshal(response) fmt.Fprintf(w, string(jsonStr)) } }
func Test_MakeAddressList(t *testing.T) { testSuite := []struct { list []*mail.Address exp string }{ { []*mail.Address{ &mail.Address{"First Last", "*****@*****.**"}, }, "\"First Last\" <*****@*****.**>", }, { []*mail.Address{ &mail.Address{"", "*****@*****.**"}, }, "<*****@*****.**>", }, { []*mail.Address{ &mail.Address{"", "*****@*****.**"}, &mail.Address{"First Last", "*****@*****.**"}, }, "<*****@*****.**>, \"First Last\" <*****@*****.**>", }, { []*mail.Address{ &mail.Address{"", "*****@*****.**"}, &mail.Address{"First Last", "*****@*****.**"}, &mail.Address{"Last", "*****@*****.**"}, &mail.Address{"", "*****@*****.**"}, }, "<*****@*****.**>, \"First Last\" <*****@*****.**>, \"Last\" <*****@*****.**>, <*****@*****.**>", }, {[]*mail.Address{}, ""}, } for _, test := range testSuite { list := MakeAddressList(test.list) if list != test.exp { t.Errorf("Expected '%s', got '%s'", test.exp, list) } if len(test.list) > 0 { parsed, err := mail.ParseAddressList(list) if err != nil || !reflect.DeepEqual(parsed, test.list) { t.Errorf("Expected to reconstruct %+v, but got %+v", test.list, parsed) } } } }
func getSenderAddress(m *Message) string { hdr := m.GetHeaderValue("From") if hdr == "" { return "" } as, err := mail.ParseAddressList(hdr) if err != nil { logger.Warning("Failed to parse sender address " + hdr) return "" } if len(as) == 0 { return "" } else if len(as) > 1 { logger.Warning("Multiple addresses found as sender address " + hdr) } return as[0].Address }
func TestAddressConvertToStrings(t *testing.T) { addresses, _ := mail.ParseAddressList(sampleAddressList) addressList := &AddressList{addresses} strAddresses := addressList.ToStrings() if len(strAddresses) != 2 { t.Error("Unexpected post-conversion length") } if strAddresses[0] != "*****@*****.**" { t.Error("First address does not match") } if strAddresses[1] != "*****@*****.**" { t.Error("Second address does not match") } }
func addressesWith(params map[string]interface{}, nm string) ([]*mail.Address, error) { o, ok := params[nm] if !ok { return nil, nil } if nil == o { return nil, nil } if s, ok := o.(string); ok { if 0 == len(s) { return nil, nil } addr, e := mail.ParseAddressList(s) if nil != e { return nil, errors.New("'" + nm + "' is invalid - " + e.Error()) } return addr, nil } if m, ok := o.(map[string]interface{}); ok { address := stringWithDefault(m, "address", "") if 0 == len(address) { return nil, errors.New("'" + nm + "' is invalid.") } return []*mail.Address{&mail.Address{Name: stringWithDefault(m, "name", ""), Address: address}}, nil } if m, ok := o.([]interface{}); ok { addresses := make([]*mail.Address, len(m)) var e error for i := range m { addresses[i], e = toAddress(m[i], nm) if nil != e { return nil, e } } return addresses, nil } return nil, errors.New("'" + nm + "' is invalid.") }
// Begins a new story with the given form inputs (authors, words) func beginPost(r request) response { authorList := strings.Join( SplitterOnAny(",\n\r").TrimResults().OmitEmpty().SplitToList(r.req.FormValue("authors")), ",") authors, err := mail.ParseAddressList(authorList) if err != nil { panic(&appError{err, "Could not parse author email addresses: " + authorList, http.StatusBadRequest}) } if len(authors) == 0 { panic(&appError{errors.New("No authors"), "No authors", http.StatusBadRequest}) } words, err := strconv.ParseUint(r.req.FormValue("words"), 10, 16) if err != nil { panic(&appError{err, "Could not parse word count as an integer", http.StatusBadRequest}) } story := newStory(r, authors, int(words)) user, _ := r.user() if user == nil || story.NextAuthor != user.Email { maybeSendMail(r.ctx(), story) } // Now issue the redirect. return redirect("/story/" + story.Id) }
// Return AddressList with RFC 2047 encoded encoded names. func (m *MIMEBody) AddressList(key string) ([]*mail.Address, error) { isAddrHeader := false for _, hkey := range AddressHeaders { if strings.ToLower(hkey) == strings.ToLower(key) { isAddrHeader = true break } } if !isAddrHeader { return nil, fmt.Errorf("%s is not address header", key) } str := DecodeToUTF8Base64Header(m.header.Get(key)) if str == "" { return nil, mail.ErrHeaderNotPresent } ret, err := mail.ParseAddressList(str) if err != nil { return nil, err } return ret, nil }
func getRecipientAddresses(m *Message) []string { as := []string{} for _, hName := range recipientHeaders { for _, hVal := range m.GetHeaders(hName) { addrs, err := mail.ParseAddressList(hVal) if err == nil { for _, addr := range addrs { as = append(as, addr.Address) } } } } if encryptToSelf { self := getSenderAddress(m) if self == "" { logger.Warning("Was unable to determine sender address while compiling recipient key list") } else { as = append(as, self) } } return as }
func (b MessageBuilder) Done() { b.Headers["MIME-Version"] = "1.0" b.Headers["Content-Type"] = "text/plain; charset=\"utf-8\"" b.Headers["Content-Transfer-Encoding"] = "base64" msg822 := []byte{} for k := range b.Headers { msg822 = append(msg822, []byte(fmt.Sprintf("%s: %s\r\n", k, b.Headers[k]))...) } msg822 = append(msg822, []byte("\r\n")...) var body bytes.Buffer encoder := base64.NewEncoder(base64.StdEncoding, &body) encoder.Write([]byte(b.Body)) encoder.Close() msg822 = append(msg822, body.Bytes()...) to_addrs, err := mail.ParseAddressList(b.Headers["To"]) if err != nil { panic(err) // FIXME } to_strs := make([]string, len(to_addrs)) for i, addr := range to_addrs { to_strs[i] = addr.Address } if len(to_strs) > 0 { // send the message out err = smtp.SendMail("localhost:25", smtp.PlainAuth("", "", "", ""), b.Headers["From"], to_strs, msg822) if err != nil { panic(err) // FIXME } } }
func main() { flag.IntVar(&port, "port", 8001, "Port to listen on") flag.StringVar(&from, "from", "", "User that the email will be from") flag.StringVar(&to, "to", "", "List of recipients for the email") flag.StringVar(&subject, "subject", "Contact Form Details", "subject") flag.StringVar(&smtpServer, "smtpserver", "", "Outgoing SMTP Server") flag.StringVar(&smtpUser, "smtpuser", "", "SMTP User") flag.StringVar(&smtpPass, "smtppass", "", "SMTP password") flag.StringVar(&corsUrl, "corsurl", "", "The URL to put in the CORS Access-Control-Allow-Origin header") flag.Parse() if smtpServer == "" || smtpUser == "" || smtpPass == "" || from == "" || to == "" { flag.PrintDefaults() return } _, err := mail.ParseAddress(from) if err != nil { log.Fatal("Error parsing From address: ", err) return } _, err = mail.ParseAddressList(to) if err != nil { log.Fatal("Error parsing To addresses: ", err) return } http.HandleFunc("/excusemeihaveaquestion", contactFormHandler) var server = ":" + strconv.Itoa(port) http.ListenAndServe(server, nil) }
// newEmailMessage will parse an imap.FieldMap into an Email. This // will expect the message to container the internaldate and the body with // all headers included. func newEmail(msgFields imap.FieldMap) (Email, error) { var email Email // parse the header var message bytes.Buffer message.Write(imap.AsBytes(msgFields["RFC822.HEADER"])) message.Write([]byte("\n\n")) rawBody := imap.AsBytes(msgFields["BODY[]"]) message.Write(rawBody) msg, err := mail.ReadMessage(&message) if err != nil { return email, fmt.Errorf("unable to read header: %s", err) } from, err := mail.ParseAddress(msg.Header.Get("From")) if err != nil { return email, fmt.Errorf("unable to parse from address: %s", err) } to, err := mail.ParseAddressList(msg.Header.Get("To")) if err != nil { return email, fmt.Errorf("unable to parse to address: %s", err) } email = Email{ Message: msg, InternalDate: imap.AsDateTime(msgFields["INTERNALDATE"]), Precedence: msg.Header.Get("Precedence"), From: from, To: to, Subject: parseSubject(msg.Header.Get("Subject")), } // chunk the body up into simple chunks email.HTML, email.Text, email.IsMultiPart, err = parseBody(msg.Header, rawBody) return email, err }
func (c *Conf) loadNotification(s *parse.SectionNode) { name := s.Name.Text if _, ok := c.Notifications[name]; ok { c.errorf("duplicate notification name: %s", name) } n := Notification{ Vars: make(map[string]string), ContentType: "application/x-www-form-urlencoded", Name: name, RunOnActions: true, } n.Text = s.RawText funcs := ttemplate.FuncMap{ "V": func(v string) string { return c.Expand(v, n.Vars, false) }, "json": func(v interface{}) string { b, err := json.Marshal(v) if err != nil { slog.Errorln(err) } return string(b) }, } c.Notifications[name] = &n pairs := c.getPairs(s, n.Vars, sNormal) for _, p := range pairs { c.at(p.node) v := p.val switch k := p.key; k { case "email": if c.SMTPHost == "" || c.EmailFrom == "" { c.errorf("email notifications require both smtpHost and emailFrom to be set") } n.email = v email, err := mail.ParseAddressList(n.email) if err != nil { c.error(err) } n.Email = email case "post": n.post = v post, err := url.Parse(n.post) if err != nil { c.error(err) } n.Post = post case "get": n.get = v get, err := url.Parse(n.get) if err != nil { c.error(err) } n.Get = get case "print": n.Print = true case "contentType": n.ContentType = v case "next": n.next = v next, ok := c.Notifications[n.next] if !ok { c.errorf("unknown notification %s", n.next) } n.Next = next case "timeout": d, err := opentsdb.ParseDuration(v) if err != nil { c.error(err) } n.Timeout = time.Duration(d) case "body": n.body = v tmpl := ttemplate.New(name).Funcs(funcs) _, err := tmpl.Parse(n.body) if err != nil { c.error(err) } n.Body = tmpl case "runOnActions": n.RunOnActions = v == "true" default: c.errorf("unknown key %s", k) } } c.at(s) if n.Timeout > 0 && n.Next == nil { c.errorf("timeout specified without next") } }
func keybindings(amua *Amua, g *gocui.Gui) error { switchToModeInt := func(mode Mode) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { return switchToMode(amua, g, mode) } } maildirMove := func(dy int) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { amua.curMaildirView.scroll(v, dy) drawSlider(amua, g) return nil } } maildirAllDown := func() func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { dy := len(amua.curMaildirView.md.messages) - amua.curMaildirView.cur - 1 amua.curMaildirView.scroll(v, dy) drawSlider(amua, g) return nil } } messageModeToggle := func(g *gocui.Gui, v *gocui.View) error { switch amua.mode { case MessageMode: switchToMode(amua, g, MessageMimeMode) case MessageMimeMode: switchToMode(amua, g, MessageMode) } return nil } search := func(forward bool) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { setStatus("Looking for: " + amua.searchPattern + " in " + amua.curMaildirView.md.path) found := false direction := 1 if forward == false { direction = -1 } for i := 0; i < len(amua.curMaildirView.md.messages); i++ { idx := ((direction * i) + amua.curMaildirView.cur + direction) % len(amua.curMaildirView.md.messages) if idx < 0 { idx = len(amua.curMaildirView.md.messages) + idx } if idx < 0 { panic(idx) } m := amua.getMessage(idx) if strings.Contains(m.Subject, amua.searchPattern) { setStatus("Found: " + amua.searchPattern + " in " + amua.curMaildirView.md.path) found = true amua.curMaildirView.curTop = idx amua.curMaildirView.cur = idx break } } if found { mv, err := g.View(MAILDIR_VIEW) if err != nil { return err } err = amua.curMaildirView.Draw(mv) if err != nil { setStatus(err.Error()) } } else { setStatus(amua.searchPattern + " not found") } return nil } } enterSearch := func(forward bool) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { v.Rewind() spbuf, err := ioutil.ReadAll(v) if err != nil { return err } prompt := amua.prompt amua.searchPattern = strings.TrimSpace(string(spbuf[len(prompt):])) switchToMode(amua, g, MaildirMode) search(forward)(g, v) return nil } } cancelSearch := func(g *gocui.Gui, v *gocui.View) error { setStatus("") return switchToMode(amua, g, amua.prevMode) } setFlag := func(flag MessageFlags) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { m := amua.curMessage() m.Flags |= flag amua.curMaildirView.Draw(v) return nil } } unsetFlag := func(flag MessageFlags) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { m := amua.curMessage() m.Flags &= ^flag amua.curMaildirView.Draw(v) return nil } } toggleFlag := func(flag MessageFlags) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { m := amua.curMessage() if (m.Flags & flag) != 0 { m.Flags &= ^flag } else { m.Flags |= flag } amua.curMaildirView.Draw(v) return nil } } quit := func(g *gocui.Gui, v *gocui.View) error { amua.applyCurMaildirChanges() return gocui.ErrQuit } deleteMessage := setFlag(Trashed) undeleteMessage := unsetFlag(Trashed) unreadMessage := unsetFlag(Seen) readMessage := setFlag(Seen) toggleFlagged := toggleFlag(Flagged) syncMaildir := func(g *gocui.Gui, v *gocui.View) error { amua.applyCurMaildirChanges() amua.RefreshMaildir(g, v) v, _ = g.View(SIDE_VIEW) drawKnownMaildirs(amua, g, v) return nil } commandEnter := func(g *gocui.Gui, v *gocui.View) error { switch amua.mode { case CommandSearchMode: return enterSearch(true)(g, v) case CommandMailModeTo: var err error amua.newMail.to, err = mail.ParseAddressList(getPromptInput()) if err != nil { displayError(err.Error()) return nil } setStatus("") switchToMode(amua, g, SendMailMode) case CommandMailModeCc: var err error amua.newMail.cc, err = mail.ParseAddressList(getPromptInput()) if err != nil { //flash error return nil } setStatus("") switchToMode(amua, g, SendMailMode) case CommandMailModeBcc: var err error amua.newMail.bcc, err = mail.ParseAddressList(getPromptInput()) if err != nil { //flash error return nil } setStatus("") switchToMode(amua, g, SendMailMode) case CommandNewMailMode: if len(amua.newMail.to) == 0 { var err error amua.newMail.to, err = mail.ParseAddressList(getPromptInput()) if err != nil { displayError(err.Error()) return nil } displayPromptWithPrefill(SUBJECT_PROMPT, amua.newMail.subject) amua.newMail.subject = "" } else if amua.newMail.subject == "" { amua.newMail.subject = getPromptInput() /* Exec $EDITOR */ tf, err := ioutil.TempFile("", "amuamail") if err != nil { log.Fatal(err) } defer os.Remove(tf.Name()) tf.Close() err = ioutil.WriteFile(tf.Name(), amua.newMail.body, 0600) if err != nil { log.Fatal(err) } cmd := exec.Command(amua.ExtEditor(), tf.Name()) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { log.Fatal(err) } amua.newMail.body, err = ioutil.ReadFile(tf.Name()) if err != nil { log.Fatal(err.Error()) } setStatus("") switchToMode(amua, g, SendMailMode) err = g.Sync() if err != nil { log.Fatal(err) } } } return nil } sendMail := func(g *gocui.Gui, v *gocui.View) error { err := send(&amua.newMail, cfg) if err != nil { setStatus(err.Error()) return nil } setStatus("Sent to " + cfg.SMTPConfig.Host) switchToMode(amua, g, MaildirMode) amua.newMail = NewMail{} return nil } reply := func(group bool) func(g *gocui.Gui, v *gocui.View) error { return func(g *gocui.Gui, v *gocui.View) error { m := amua.curMessage() amua.newMail.to = buildTo(m) if group { amua.newMail.cc = buildCCs(m) } amua.newMail.subject = "Re: " + m.Subject buf, err := ioutil.ReadAll((*MessageAsText)(m)) if err != nil { return err } buf = bytes.Replace(buf, []byte("\n"), []byte("\n> "), -1) replyHeader := fmt.Sprintf("On %s, %s wrote:\n> ", m.Date.Format("Mon Jan 2 15:04:05 -0700 MST 2006"), m.From) amua.newMail.body = append([]byte(replyHeader), buf...) switchToMode(amua, g, CommandNewMailMode) return nil } } replyMessage := reply(false) groupReplyMessage := reply(true) pipeMessage := func(g *gocui.Gui, v *gocui.View) error { m := amua.curMessage() cmd := exec.Command(amua.ExtEditor(), m.path) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout if err := cmd.Run(); err != nil { log.Fatal(err) } setStatus("") switchToMode(amua, g, MaildirMode) err := g.Sync() if err != nil { log.Fatal(err) } /* exec a command, pipe the message there */ return nil } type keybinding struct { key interface{} fn gocui.KeybindingHandler mod bool } bindings := map[string][]keybinding{ MAILDIR_VIEW: { {gocui.KeyEnter, switchToModeInt(MessageMode), false}, {'c', switchToModeInt(KnownMaildirsMode), false}, {'v', switchToModeInt(MessageMimeMode), false}, {'q', quit, false}, {'d', deleteMessage, false}, {'u', undeleteMessage, false}, {'G', maildirAllDown(), false}, {'k', maildirMove(-1), false}, {gocui.KeyArrowUp, maildirMove(-1), false}, {'j', maildirMove(1), false}, {'n', search(true), false}, {'N', search(false), false}, {'$', syncMaildir, false}, {'F', toggleFlagged, false}, {gocui.KeyCtrlR, readMessage, false}, {gocui.KeyCtrlN, unreadMessage, false}, {gocui.KeyArrowDown, maildirMove(1), false}, {gocui.KeyCtrlF, maildirMove(10), false}, {gocui.KeyPgdn, maildirMove(10), false}, {gocui.KeyCtrlB, maildirMove(-10), false}, {gocui.KeyPgup, maildirMove(-10), false}, {'/', switchToModeInt(CommandSearchMode), false}, {'m', switchToModeInt(CommandNewMailMode), false}, {'r', replyMessage, false}, {'g', groupReplyMessage, false}, {'|', pipeMessage, false}, }, MESSAGE_VIEW: { {'q', switchToModeInt(MaildirMode), false}, {'v', messageModeToggle, false}, {gocui.KeyPgup, scrollMessageView(-10), false}, {gocui.KeyPgdn, scrollMessageView(10), false}, {gocui.KeySpace, scrollMessageView(10), false}, {'j', scrollMessageView(1), false}, {'k', scrollMessageView(-1), false}, {'r', replyMessage, false}, {'g', groupReplyMessage, false}, {'|', pipeMessage, false}, }, SEND_MAIL_VIEW: { {'q', switchToModeInt(MaildirMode), false}, {'t', switchToModeInt(CommandMailModeTo), false}, {'c', switchToModeInt(CommandMailModeCc), false}, {'b', switchToModeInt(CommandMailModeBcc), false}, {'y', sendMail, false}, {gocui.KeyCtrlG, switchToModeInt(MaildirMode), false}, }, STATUS_VIEW: { {gocui.KeyEnter, commandEnter, false}, {gocui.KeyCtrlG, cancelSearch, false}, }, SIDE_VIEW: { {'j', scrollSideView(amua, 1), false}, {gocui.KeyArrowDown, scrollSideView(amua, 1), false}, {'k', scrollSideView(amua, -1), false}, {gocui.KeyArrowUp, scrollSideView(amua, 1), false}, {gocui.KeyEnter, selectNewMaildir(amua), false}, }, "": { {gocui.KeyCtrlC, quit, false}, }, } for vn, binds := range bindings { for _, b := range binds { err := g.SetKeybinding(vn, b.key, gocui.ModNone, b.fn) if err != nil { return err } } } return nil }