aboutsummaryrefslogtreecommitdiff
path: root/plugins/base
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/base')
-rwxr-xr-xplugins/base/imap.go45
-rw-r--r--plugins/base/public/compose.html1
-rw-r--r--plugins/base/routes.go95
3 files changed, 97 insertions, 44 deletions
diff --git a/plugins/base/imap.go b/plugins/base/imap.go
index 0c39ca6..a9de217 100755
--- a/plugins/base/imap.go
+++ b/plugins/base/imap.go
@@ -2,12 +2,15 @@ package koushinbase
import (
"bufio"
+ "bytes"
"fmt"
"sort"
"strconv"
"strings"
+ "time"
"github.com/emersion/go-imap"
+ imapspecialuse "github.com/emersion/go-imap-specialuse"
imapclient "github.com/emersion/go-imap/client"
"github.com/emersion/go-message"
"github.com/emersion/go-message/textproto"
@@ -375,3 +378,45 @@ func markMessageAnswered(conn *imapclient.Client, mboxName string, uid uint32) e
flags := []interface{}{imap.AnsweredFlag}
return conn.UidStore(seqSet, item, flags, nil)
}
+
+type mailboxType int
+
+const (
+ mailboxSent mailboxType = iota
+ mailboxDrafts
+)
+
+func appendMessage(c *imapclient.Client, msg *OutgoingMessage, mboxType mailboxType) (saved bool, err error) {
+ var mboxAttr string
+ switch mboxType {
+ case mailboxSent:
+ mboxAttr = imapspecialuse.Sent
+ case mailboxDrafts:
+ mboxAttr = imapspecialuse.Drafts
+ }
+
+ mbox, err := getMailboxByAttribute(c, mboxAttr)
+ if err != nil {
+ return false, err
+ }
+ if mbox == nil {
+ return false, nil
+ }
+
+ // IMAP needs to know in advance the final size of the message, so
+ // there's no way around storing it in a buffer here.
+ var buf bytes.Buffer
+ if err := msg.WriteTo(&buf); err != nil {
+ return false, err
+ }
+
+ flags := []string{imap.SeenFlag}
+ if mboxType == mailboxDrafts {
+ flags = append(flags, imap.DraftFlag)
+ }
+ if err := c.Append(mbox.Name, flags, time.Now(), &buf); err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
diff --git a/plugins/base/public/compose.html b/plugins/base/public/compose.html
index 0e16acf..4d69c81 100644
--- a/plugins/base/public/compose.html
+++ b/plugins/base/public/compose.html
@@ -26,6 +26,7 @@
<label for="attachments">Attachments:</label>
<input type="file" name="attachments" id="attachments" multiple>
<br><br>
+ <input type="submit" name="save_as_draft" value="Save as draft">
<input type="submit" value="Send">
</form>
diff --git a/plugins/base/routes.go b/plugins/base/routes.go
index 961f25a..9eaf18a 100644
--- a/plugins/base/routes.go
+++ b/plugins/base/routes.go
@@ -1,7 +1,6 @@
package koushinbase
import (
- "bytes"
"fmt"
"io/ioutil"
"mime"
@@ -9,12 +8,10 @@ import (
"net/url"
"strconv"
"strings"
- "time"
"git.sr.ht/~emersion/koushin"
"github.com/emersion/go-imap"
imapmove "github.com/emersion/go-imap-move"
- imapspecialuse "github.com/emersion/go-imap-specialuse"
imapclient "github.com/emersion/go-imap/client"
"github.com/emersion/go-message"
"github.com/emersion/go-smtp"
@@ -281,6 +278,39 @@ type ComposeRenderData struct {
Message *OutgoingMessage
}
+// Send message, append it to the Sent mailbox, mark the original message as
+// answered
+func submitCompose(ctx *koushin.Context, msg *OutgoingMessage, inReplyToMboxName string, inReplyToUid uint32) error {
+ err := ctx.Session.DoSMTP(func(c *smtp.Client) error {
+ return sendMessage(c, msg)
+ })
+ if err != nil {
+ if _, ok := err.(koushin.AuthError); ok {
+ return echo.NewHTTPError(http.StatusForbidden, err)
+ }
+ return fmt.Errorf("failed to send message: %v", err)
+ }
+
+ if inReplyToUid != 0 {
+ err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
+ return markMessageAnswered(c, inReplyToMboxName, inReplyToUid)
+ })
+ if err != nil {
+ return fmt.Errorf("failed to mark original message as answered: %v", err)
+ }
+ }
+
+ err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
+ _, err := appendMessage(c, msg, mailboxSent)
+ return err
+ })
+ if err != nil {
+ return fmt.Errorf("failed to save message to Sent mailbox: %v", err)
+ }
+
+ return ctx.Redirect(http.StatusFound, "/mailbox/INBOX")
+}
+
func handleCompose(ctx *koushin.Context) error {
var msg OutgoingMessage
if strings.ContainsRune(ctx.Session.Username(), '@') {
@@ -355,6 +385,12 @@ func handleCompose(ctx *koushin.Context) error {
}
if ctx.Request().Method == http.MethodPost {
+ formParams, err := ctx.FormParams()
+ if err != nil {
+ return fmt.Errorf("failed to parse form: %v", err)
+ }
+ _, saveAsDraft := formParams["save_as_draft"]
+
msg.From = ctx.FormValue("from")
msg.To = parseAddressList(ctx.FormValue("to"))
msg.Subject = ctx.FormValue("subject")
@@ -367,52 +403,23 @@ func handleCompose(ctx *koushin.Context) error {
}
msg.Attachments = form.File["attachments"]
- err = ctx.Session.DoSMTP(func(c *smtp.Client) error {
- return sendMessage(c, &msg)
- })
- if err != nil {
- if _, ok := err.(koushin.AuthError); ok {
- return echo.NewHTTPError(http.StatusForbidden, err)
- }
- return fmt.Errorf("failed to send message: %v", err)
- }
-
- if inReplyToUid != 0 {
+ if saveAsDraft {
err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
- return markMessageAnswered(c, inReplyToMboxName, inReplyToUid)
+ copied, err := appendMessage(c, &msg, mailboxDrafts)
+ if err != nil {
+ return err
+ }
+ if !copied {
+ return fmt.Errorf("no Draft mailbox found")
+ }
+ return nil
})
if err != nil {
- return fmt.Errorf("failed to mark original message as answered: %v", err)
- }
- }
-
- err = ctx.Session.DoIMAP(func(c *imapclient.Client) error {
- mbox, err := getMailboxByAttribute(c, imapspecialuse.Sent)
- if err != nil {
- return err
+ return fmt.Errorf("failed to save message to Draft mailbox: %v", err)
}
- if mbox == nil {
- return nil
- }
-
- // IMAP needs to know in advance the final size of the message, so
- // there's no way around storing it in a buffer here.
- var buf bytes.Buffer
- if err := msg.WriteTo(&buf); err != nil {
- return err
- }
-
- flags := []string{imap.SeenFlag}
- return c.Append(mbox.Name, flags, time.Now(), &buf)
- })
- if err != nil {
- return fmt.Errorf("failed to save message to Sent mailbox: %v", err)
+ } else {
+ return submitCompose(ctx, &msg, inReplyToMboxName, inReplyToUid)
}
-
- // TODO: append to IMAP Sent mailbox
- // TODO: add \Answered flag to original IMAP message
-
- return ctx.Redirect(http.StatusFound, "/mailbox/INBOX")
}
return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{