mox/webops/export.go
Mechiel Lukkien bf5cfca6b9
webmail: add export functionality
per mailbox, or for all mailboxes, in maildir/mbox format, in tar/tgz/zip
archive or without archive format for single mbox, single or recursive. the
webaccount already had an option to export all mailboxes, it now looks similar
to the webmail version.
2024-04-22 13:41:40 +02:00

96 lines
2.8 KiB
Go

package webops
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"mime"
"net/http"
"strings"
"time"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/store"
)
// Export is used by webmail and webaccount to export messages of one or
// multiple mailboxes, in maildir or mbox format, in a tar/tgz/zip archive or
// direct mbox.
func Export(log mlog.Log, accName string, w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "405 - method not allowed - use post", http.StatusMethodNotAllowed)
return
}
mailbox := r.FormValue("mailbox") // Empty means all.
format := r.FormValue("format")
archive := r.FormValue("archive")
recursive := r.FormValue("recursive") != ""
switch format {
case "maildir", "mbox":
default:
http.Error(w, "400 - bad request - unknown format", http.StatusBadRequest)
return
}
switch archive {
case "none", "tar", "tgz", "zip":
default:
http.Error(w, "400 - bad request - unknown archive", http.StatusBadRequest)
return
}
if archive == "none" && (format != "mbox" || recursive) {
http.Error(w, "400 - bad request - archive none can only be used with non-recursive mbox", http.StatusBadRequest)
return
}
acc, err := store.OpenAccount(log, accName)
if err != nil {
log.Errorx("open account for export", err)
http.Error(w, "500 - internal server error", http.StatusInternalServerError)
return
}
defer func() {
err := acc.Close()
log.Check(err, "closing account")
}()
name := strings.ReplaceAll(mailbox, "/", "-")
if name == "" {
name = "all"
}
filename := fmt.Sprintf("mailexport-%s-%s", name, time.Now().Format("20060102-150405"))
filename += "." + format
var archiver store.Archiver
if archive == "none" {
w.Header().Set("Content-Type", "application/mbox")
archiver = &store.MboxArchiver{Writer: w}
} else if archive == "tar" {
// Don't tempt browsers to "helpfully" decompress.
w.Header().Set("Content-Type", "application/x-tar")
archiver = store.TarArchiver{Writer: tar.NewWriter(w)}
filename += ".tar"
} else if archive == "tgz" {
// Don't tempt browsers to "helpfully" decompress.
w.Header().Set("Content-Type", "application/octet-stream")
gzw := gzip.NewWriter(w)
defer func() {
_ = gzw.Close()
}()
archiver = store.TarArchiver{Writer: tar.NewWriter(gzw)}
filename += ".tgz"
} else {
w.Header().Set("Content-Type", "application/zip")
archiver = store.ZipArchiver{Writer: zip.NewWriter(w)}
filename += ".zip"
}
defer func() {
err := archiver.Close()
log.Check(err, "exporting mail close")
}()
w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename}))
if err := store.ExportMessages(r.Context(), log, acc.DB, acc.Dir, archiver, format == "maildir", mailbox, recursive); err != nil {
log.Errorx("exporting mail", err)
}
}