A webmail client. Forked from https://git.sr.ht/~migadu/alps
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

210 satır
4.4 KiB

  1. package alpsbase
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "mime"
  9. "mime/multipart"
  10. "strings"
  11. "time"
  12. "github.com/emersion/go-message/mail"
  13. "github.com/emersion/go-smtp"
  14. )
  15. func quote(r io.Reader) (string, error) {
  16. scanner := bufio.NewScanner(r)
  17. var builder strings.Builder
  18. for scanner.Scan() {
  19. builder.WriteString("> ")
  20. builder.Write(scanner.Bytes())
  21. builder.WriteString("\n")
  22. }
  23. if err := scanner.Err(); err != nil {
  24. return "", fmt.Errorf("quote: failed to read original message: %s", err)
  25. }
  26. return builder.String(), nil
  27. }
  28. type Attachment interface {
  29. MIMEType() string
  30. Filename() string
  31. Open() (io.ReadCloser, error)
  32. }
  33. type formAttachment struct {
  34. *multipart.FileHeader
  35. }
  36. func (att *formAttachment) Open() (io.ReadCloser, error) {
  37. return att.FileHeader.Open()
  38. }
  39. func (att *formAttachment) MIMEType() string {
  40. // TODO: retain params, e.g. "charset"?
  41. t, _, _ := mime.ParseMediaType(att.FileHeader.Header.Get("Content-Type"))
  42. return t
  43. }
  44. func (att *formAttachment) Filename() string {
  45. return att.FileHeader.Filename
  46. }
  47. type imapAttachment struct {
  48. Mailbox string
  49. Uid uint32
  50. Node *IMAPPartNode
  51. Body []byte
  52. }
  53. func (att *imapAttachment) Open() (io.ReadCloser, error) {
  54. if att.Body == nil {
  55. return nil, fmt.Errorf("IMAP attachment has not been pre-fetched")
  56. }
  57. return ioutil.NopCloser(bytes.NewReader(att.Body)), nil
  58. }
  59. func (att *imapAttachment) MIMEType() string {
  60. return att.Node.MIMEType
  61. }
  62. func (att *imapAttachment) Filename() string {
  63. return att.Node.Filename
  64. }
  65. type OutgoingMessage struct {
  66. From string
  67. To []string
  68. Subject string
  69. MessageID string
  70. InReplyTo string
  71. Text string
  72. Attachments []Attachment
  73. }
  74. func (msg *OutgoingMessage) ToString() string {
  75. return strings.Join(msg.To, ", ")
  76. }
  77. func writeAttachment(mw *mail.Writer, att Attachment) error {
  78. var h mail.AttachmentHeader
  79. h.SetContentType(att.MIMEType(), nil)
  80. h.SetFilename(att.Filename())
  81. aw, err := mw.CreateAttachment(h)
  82. if err != nil {
  83. return fmt.Errorf("failed to create attachment: %v", err)
  84. }
  85. defer aw.Close()
  86. f, err := att.Open()
  87. if err != nil {
  88. return fmt.Errorf("failed to open attachment: %v", err)
  89. }
  90. defer f.Close()
  91. if _, err := io.Copy(aw, f); err != nil {
  92. return fmt.Errorf("failed to write attachment: %v", err)
  93. }
  94. if err := f.Close(); err != nil {
  95. return fmt.Errorf("failed to close attachment: %v", err)
  96. }
  97. if err := aw.Close(); err != nil {
  98. return fmt.Errorf("failed to close attachment writer: %v", err)
  99. }
  100. return nil
  101. }
  102. func (msg *OutgoingMessage) WriteTo(w io.Writer) error {
  103. from := []*mail.Address{{"", msg.From}}
  104. to := make([]*mail.Address, len(msg.To))
  105. for i, addr := range msg.To {
  106. to[i] = &mail.Address{"", addr}
  107. }
  108. var h mail.Header
  109. h.SetDate(time.Now())
  110. h.SetAddressList("From", from)
  111. h.SetAddressList("To", to)
  112. if msg.Subject != "" {
  113. h.SetText("Subject", msg.Subject)
  114. }
  115. if msg.InReplyTo != "" {
  116. h.Set("In-Reply-To", msg.InReplyTo)
  117. }
  118. h.Set("Message-Id", msg.MessageID)
  119. if msg.MessageID == "" {
  120. panic(fmt.Errorf("Attempting to send message without message ID"))
  121. }
  122. mw, err := mail.CreateWriter(w, h)
  123. if err != nil {
  124. return fmt.Errorf("failed to create mail writer: %v", err)
  125. }
  126. var th mail.InlineHeader
  127. th.Set("Content-Type", "text/plain; charset=utf-8")
  128. tw, err := mw.CreateSingleInline(th)
  129. if err != nil {
  130. return fmt.Errorf("failed to create text part: %v", err)
  131. }
  132. defer tw.Close()
  133. if _, err := io.WriteString(tw, msg.Text); err != nil {
  134. return fmt.Errorf("failed to write text part: %v", err)
  135. }
  136. if err := tw.Close(); err != nil {
  137. return fmt.Errorf("failed to close text part: %v", err)
  138. }
  139. for _, att := range msg.Attachments {
  140. if err := writeAttachment(mw, att); err != nil {
  141. return err
  142. }
  143. }
  144. if err := mw.Close(); err != nil {
  145. return fmt.Errorf("failed to close mail writer: %v", err)
  146. }
  147. return nil
  148. }
  149. func sendMessage(c *smtp.Client, msg *OutgoingMessage) error {
  150. if err := c.Mail(msg.From, nil); err != nil {
  151. return fmt.Errorf("MAIL FROM failed: %v", err)
  152. }
  153. for _, to := range msg.To {
  154. if err := c.Rcpt(to); err != nil {
  155. return fmt.Errorf("RCPT TO failed: %v", err)
  156. }
  157. }
  158. w, err := c.Data()
  159. if err != nil {
  160. return fmt.Errorf("DATA failed: %v", err)
  161. }
  162. defer w.Close()
  163. if err := msg.WriteTo(w); err != nil {
  164. return fmt.Errorf("failed to write outgoing message: %v", err)
  165. }
  166. if err := w.Close(); err != nil {
  167. return fmt.Errorf("failed to close SMTP data writer: %v", err)
  168. }
  169. return nil
  170. }