Add export-messages command

Right now only supports exporting conversations, and doesn't support
attachments.
This commit is contained in:
Simon Ser 2020-09-14 12:46:47 +02:00
parent ea188ff133
commit bd861cdb00
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
4 changed files with 115 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import (
imapmove "github.com/emersion/go-imap-move"
imapspacialuse "github.com/emersion/go-imap-specialuse"
imapserver "github.com/emersion/go-imap/server"
"github.com/emersion/go-mbox"
"github.com/emersion/go-smtp"
"github.com/howeyc/gopass"
"golang.org/x/crypto/openpgp"
@ -20,6 +21,7 @@ import (
"github.com/emersion/hydroxide/auth"
"github.com/emersion/hydroxide/carddav"
"github.com/emersion/hydroxide/events"
"github.com/emersion/hydroxide/exports"
imapbackend "github.com/emersion/hydroxide/imap"
"github.com/emersion/hydroxide/imports"
"github.com/emersion/hydroxide/protonmail"
@ -116,6 +118,7 @@ Commands:
export-secret-keys <username> Export secret keys
imap Run hydroxide as an IMAP server
import-messages <username> <file> Import messages
export-messages [options...] <username> Export messages
serve Run all servers
smtp Run hydroxide as an SMTP server
status View hydroxide status
@ -151,6 +154,7 @@ func main() {
authCmd := flag.NewFlagSet("auth", flag.ExitOnError)
exportSecretKeysCmd := flag.NewFlagSet("export-secret-keys", flag.ExitOnError)
importMessagesCmd := flag.NewFlagSet("import-messages", flag.ExitOnError)
exportMessagesCmd := flag.NewFlagSet("export-messages", flag.ExitOnError)
flag.Parse()
@ -335,6 +339,38 @@ func main() {
if err := imports.ImportMessage(c, f); err != nil {
log.Fatal(err)
}
case "export-messages":
var convID string
exportMessagesCmd.StringVar(&convID, "conversation-id", "", "conversation ID")
exportMessagesCmd.Parse(flag.Args()[1:])
username := exportMessagesCmd.Arg(0)
log.Println(convID, username)
if convID == "" || username == "" {
log.Fatal("usage: hydroxide export-messages -conversation-id <id> <username>")
}
var bridgePassword string
fmt.Fprintf(os.Stderr, "Bridge password: ")
if pass, err := gopass.GetPasswd(); err != nil {
log.Fatal(err)
} else {
bridgePassword = string(pass)
}
c, privateKeys, err := auth.NewManager(newClient).Auth(username, bridgePassword)
if err != nil {
log.Fatal(err)
}
mboxWriter := mbox.NewWriter(os.Stdout)
if err := exports.ExportConversation(c, privateKeys, mboxWriter, convID); err != nil {
log.Fatal(err)
}
if err := mboxWriter.Close(); err != nil {
log.Fatal(err)
}
case "smtp":
addr := *smtpHost + ":" + *smtpPort
authManager := auth.NewManager(newClient)

76
exports/messages.go Normal file
View File

@ -0,0 +1,76 @@
package exports
import (
"fmt"
"io"
"bufio"
"strings"
"github.com/emersion/go-mbox"
"github.com/emersion/go-message"
"github.com/emersion/go-message/mail"
"github.com/emersion/go-message/textproto"
"golang.org/x/crypto/openpgp"
"github.com/emersion/hydroxide/protonmail"
)
func ExportMessage(c *protonmail.Client, privateKeys openpgp.KeyRing, w io.Writer, id string) error {
msg, err := c.GetMessage(id)
if err != nil {
return fmt.Errorf("failed to fetch message: %v", err)
}
mimeType := msg.MIMEType
if mimeType == "" {
mimeType = "text/html"
}
br := bufio.NewReader(strings.NewReader(msg.Header))
th, err := textproto.ReadHeader(br)
if err != nil {
return fmt.Errorf("failed to read message header: %v", err)
}
mh := mail.Header{message.Header{th}}
mh.SetContentType(mimeType, nil)
mh.Set("Content-Transfer-Encoding", "quoted-printable")
// TODO: add support for attachments
mw, err := mail.CreateSingleInlineWriter(w, mh)
if err != nil {
return fmt.Errorf("failed to create message writer: %v", err)
}
md, err := msg.Read(privateKeys, nil)
if err != nil {
return err
}
// TODO: check signature
if _, err := io.Copy(mw, md.UnverifiedBody); err != nil {
return err
}
return mw.Close()
}
func ExportConversation(c *protonmail.Client, privateKeys openpgp.KeyRing, mbox *mbox.Writer, id string) error {
_, msgs, err := c.GetConversation(id, "")
if err != nil {
return fmt.Errorf("failed to fetch conversation: %v", err)
}
for _, msg := range msgs {
w, err := mbox.CreateMessage(msg.Sender.Address, msg.Time.Time())
if err != nil {
return fmt.Errorf("failed to create mbox message: %v", err)
}
if err := ExportMessage(c, privateKeys, w, msg.ID); err != nil {
return fmt.Errorf("failed to export conversation message: %v", err)
}
}
return nil
}

1
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/emersion/go-imap v1.0.4
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62
github.com/emersion/go-mbox v1.0.0
github.com/emersion/go-message v0.11.1
github.com/emersion/go-smtp v0.12.1
github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7

2
go.sum
View File

@ -16,6 +16,8 @@ github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1Po
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62 h1:4ZAfwfc8aDlj26kkEap1UDSwwDnJp9Ie8Uj1MSXAkPk=
github.com/emersion/go-imap-specialuse v0.0.0-20161227184202-ba031ced6a62/go.mod h1:/nybxhI8kXom8Tw6BrHMl42usALvka6meORflnnYwe4=
github.com/emersion/go-mbox v1.0.0 h1:HN6aKbyqmgIfK9fS/gen+NRr2wXLSxZXWfdAIAnzQPc=
github.com/emersion/go-mbox v1.0.0/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
github.com/emersion/go-message v0.11.1 h1:0C/S4JIXDTSfXB1vpqdimAYyK4+79fgEAMQ0dSL+Kac=
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=