mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 17:47:03 +00:00
- ui: Fix favicon - webui: Changes to support serving Elm UI - Static files now served from `/` mount point. - Old UI handlers moved to `/serve` mount point, some will still be needed by the Elm UI; safe HTML and attachments for example. - Update dev-start.sh for new UI, with tip on how to build it. - ui: Detect browser host:port for websocket URL, - webui: Remove unused mailbox handlers, rename routes - Many routes not needed by Elm UI. - `/serve/mailbox/*` becomes `/serve/m/*`. - webui: Impl custom JSON message API for web UI, - ui: Refactor Mailbox view functions, - ui: Add body tabs for safe HTML and plain text, - webui: Format plain text for new UI, - ui: List attachments with view & download links,
183 lines
5.5 KiB
Go
183 lines
5.5 KiB
Go
package webui
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/jhillyerd/inbucket/pkg/server/web"
|
|
"github.com/jhillyerd/inbucket/pkg/storage"
|
|
"github.com/jhillyerd/inbucket/pkg/stringutil"
|
|
"github.com/jhillyerd/inbucket/pkg/webui/sanitize"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// JSONMessage formats message data for the UI.
|
|
type JSONMessage struct {
|
|
Mailbox string `json:"mailbox"`
|
|
ID string `json:"id"`
|
|
From string `json:"from"`
|
|
To []string `json:"to"`
|
|
Subject string `json:"subject"`
|
|
Date time.Time `json:"date"`
|
|
Size int64 `json:"size"`
|
|
Seen bool `json:"seen"`
|
|
Header map[string][]string `json:"header"`
|
|
Text string `json:"text"`
|
|
HTML string `json:"html"`
|
|
Attachments []*JSONAttachment `json:"attachments"`
|
|
}
|
|
|
|
// JSONAttachment formats attachment data for the UI.
|
|
type JSONAttachment struct {
|
|
ID string `json:"id"`
|
|
FileName string `json:"filename"`
|
|
ContentType string `json:"content-type"`
|
|
}
|
|
|
|
// MailboxMessage outputs a particular message as JSON for the UI.
|
|
func MailboxMessage(w http.ResponseWriter, req *http.Request, ctx *web.Context) (err error) {
|
|
id := ctx.Vars["id"]
|
|
name, err := ctx.Manager.MailboxForAddress(ctx.Vars["name"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
msg, err := ctx.Manager.GetMessage(name, id)
|
|
if err != nil && err != storage.ErrNotExist {
|
|
return fmt.Errorf("GetMessage(%q) failed: %v", id, err)
|
|
}
|
|
if msg == nil {
|
|
http.NotFound(w, req)
|
|
return nil
|
|
}
|
|
attachParts := msg.Attachments()
|
|
attachments := make([]*JSONAttachment, len(attachParts))
|
|
for i, part := range attachParts {
|
|
attachments[i] = &JSONAttachment{
|
|
ID: strconv.Itoa(i),
|
|
FileName: part.FileName,
|
|
ContentType: part.ContentType,
|
|
}
|
|
}
|
|
// Sanitize HTML body.
|
|
htmlBody := ""
|
|
if msg.HTML() != "" {
|
|
if str, err := sanitize.HTML(msg.HTML()); err == nil {
|
|
htmlBody = str
|
|
} else {
|
|
htmlBody = "Inbucket HTML sanitizer failed."
|
|
log.Warn().Str("module", "webui").Str("mailbox", name).Str("id", id).Err(err).
|
|
Msg("HTML sanitizer failed")
|
|
}
|
|
}
|
|
return web.RenderJSON(w,
|
|
&JSONMessage{
|
|
Mailbox: name,
|
|
ID: msg.ID,
|
|
From: msg.From.String(),
|
|
To: stringutil.StringAddressList(msg.To),
|
|
Subject: msg.Subject,
|
|
Date: msg.Date,
|
|
Size: msg.Size,
|
|
Seen: msg.Seen,
|
|
Header: msg.Header(),
|
|
Text: web.TextToHTML(msg.Text()),
|
|
HTML: htmlBody,
|
|
Attachments: attachments,
|
|
})
|
|
}
|
|
|
|
// MailboxHTML displays the HTML content of a message. Renders a partial
|
|
func MailboxHTML(w http.ResponseWriter, req *http.Request, ctx *web.Context) (err error) {
|
|
// Don't have to validate these aren't empty, Gorilla returns 404
|
|
id := ctx.Vars["id"]
|
|
name, err := ctx.Manager.MailboxForAddress(ctx.Vars["name"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
msg, err := ctx.Manager.GetMessage(name, id)
|
|
if err == storage.ErrNotExist {
|
|
http.NotFound(w, req)
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
// This doesn't indicate empty, likely an IO error
|
|
return fmt.Errorf("GetMessage(%q) failed: %v", id, err)
|
|
}
|
|
// Render partial template
|
|
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
|
|
return web.RenderPartial("mailbox/_html.html", w, map[string]interface{}{
|
|
"ctx": ctx,
|
|
"name": name,
|
|
"message": msg,
|
|
"body": template.HTML(msg.HTML()),
|
|
})
|
|
}
|
|
|
|
// MailboxSource displays the raw source of a message, including headers. Renders text/plain
|
|
func MailboxSource(w http.ResponseWriter, req *http.Request, ctx *web.Context) (err error) {
|
|
// Don't have to validate these aren't empty, Gorilla returns 404
|
|
id := ctx.Vars["id"]
|
|
name, err := ctx.Manager.MailboxForAddress(ctx.Vars["name"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r, err := ctx.Manager.SourceReader(name, id)
|
|
if err == storage.ErrNotExist {
|
|
http.NotFound(w, req)
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
// This doesn't indicate missing, likely an IO error
|
|
return fmt.Errorf("SourceReader(%q) failed: %v", id, err)
|
|
}
|
|
// Output message source
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
_, err = io.Copy(w, r)
|
|
return err
|
|
}
|
|
|
|
// MailboxViewAttach sends the attachment to the client for online viewing
|
|
func MailboxViewAttach(w http.ResponseWriter, req *http.Request, ctx *web.Context) (err error) {
|
|
// Don't have to validate these aren't empty, Gorilla returns 404
|
|
name, err := ctx.Manager.MailboxForAddress(ctx.Vars["name"])
|
|
if err != nil {
|
|
ctx.Session.AddFlash(err.Error(), "errors")
|
|
_ = ctx.Session.Save(req, w)
|
|
http.Redirect(w, req, web.Reverse("RootIndex"), http.StatusSeeOther)
|
|
return nil
|
|
}
|
|
id := ctx.Vars["id"]
|
|
numStr := ctx.Vars["num"]
|
|
num, err := strconv.ParseUint(numStr, 10, 32)
|
|
if err != nil {
|
|
ctx.Session.AddFlash("Attachment number must be unsigned numeric", "errors")
|
|
_ = ctx.Session.Save(req, w)
|
|
http.Redirect(w, req, web.Reverse("RootIndex"), http.StatusSeeOther)
|
|
return nil
|
|
}
|
|
msg, err := ctx.Manager.GetMessage(name, id)
|
|
if err == storage.ErrNotExist {
|
|
http.NotFound(w, req)
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
// This doesn't indicate empty, likely an IO error
|
|
return fmt.Errorf("GetMessage(%q) failed: %v", id, err)
|
|
}
|
|
if int(num) >= len(msg.Attachments()) {
|
|
ctx.Session.AddFlash("Attachment number too high", "errors")
|
|
_ = ctx.Session.Save(req, w)
|
|
http.Redirect(w, req, web.Reverse("RootIndex"), http.StatusSeeOther)
|
|
return nil
|
|
}
|
|
// Output attachment
|
|
part := msg.Attachments()[num]
|
|
w.Header().Set("Content-Type", part.ContentType)
|
|
_, err = w.Write(part.Content)
|
|
return err
|
|
}
|