diff --git a/internal/tools/html.go b/internal/tools/html.go
index 6e5e883..753835b 100644
--- a/internal/tools/html.go
+++ b/internal/tools/html.go
@@ -17,3 +17,34 @@ func GetHTMLAttributeVal(e *html.Node, key string) (string, error) {
return "", fmt.Errorf("%s not found", key)
}
+
+// SetHTMLAttributeVal sets an attribute on a node.
+func SetHTMLAttributeVal(n *html.Node, key, val string) {
+ for i := range n.Attr {
+ a := &n.Attr[i]
+ if a.Key == key {
+ a.Val = val
+ return
+ }
+ }
+ n.Attr = append(n.Attr, html.Attribute{
+ Key: key,
+ Val: val,
+ })
+}
+
+// WalkHTML traverses the entire HTML tree and calls fn on each node.
+func WalkHTML(n *html.Node, fn func(*html.Node)) {
+ if n == nil {
+ return
+ }
+
+ fn(n)
+
+ // Each node has a pointer to its first child and next sibling. To traverse
+ // all children of a node, we need to start from its first child and then
+ // traverse the next sibling until nil.
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ WalkHTML(c, fn)
+ }
+}
diff --git a/server/apiv1/testing.go b/server/apiv1/testing.go
index 7c005d5..6eadbc8 100644
--- a/server/apiv1/testing.go
+++ b/server/apiv1/testing.go
@@ -1,14 +1,19 @@
package apiv1
import (
+ "bytes"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/axllent/mailpit/config"
+ "github.com/axllent/mailpit/internal/logger"
"github.com/axllent/mailpit/internal/storage"
+ "github.com/axllent/mailpit/internal/tools"
"github.com/gorilla/mux"
+ "golang.org/x/net/html"
+ "golang.org/x/net/html/atom"
)
// swagger:parameters GetMessageHTMLParams
@@ -17,7 +22,19 @@ type getMessageHTMLParams struct {
//
// in: path
// required: true
+ // example: B79PgsotENzGwk4CCbAcAq
ID string
+
+ // If this is route is to be embedded in an iframe, set embed to `1` in the URL to add `target="_blank"` and `rel="noreferrer noopener"` to all links.
+ //
+ // In addition, a small script will be added to the end of the document to post (postMessage()) the height of the document back to the parent window for optional iframe height resizing.
+ //
+ // Note that this will also *transform* the message into a full HTML document (if it isn't already), so this option is useful for viewing but not programmatic testing.
+ //
+ // in: query
+ // required: false
+ // type: string
+ Embed string `json:"embed"`
}
// GetMessageHTML (method: GET) returns a rendered version of a message's HTML part
@@ -68,9 +85,43 @@ func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
return
}
- html := linkInlineImages(msg)
+ htmlStr := linkInlineImages(msg)
+
+ // If embed=1 is set, then we will add target="_blank" and rel="noreferrer noopener" to all links
+ if r.URL.Query().Get("embed") == "1" {
+ doc, err := html.Parse(strings.NewReader(htmlStr))
+ if err != nil {
+ logger.Log().Error(err.Error())
+ } else {
+ // Walk the entire HTML tree.
+ tools.WalkHTML(doc, func(n *html.Node) {
+ if n.Type == html.ElementNode && n.DataAtom == atom.A {
+ // Set attributes on all anchors with external links.
+ tools.SetHTMLAttributeVal(n, "target", "_blank")
+ tools.SetHTMLAttributeVal(n, "rel", "noreferrer noopener")
+ }
+
+ b := bytes.Buffer{}
+ html.Render(&b, doc)
+ htmlStr = b.String()
+ })
+
+ nonce := r.Header.Get("mp-nonce")
+
+ js := ``
+
+ htmlStr = strings.ReplaceAll(htmlStr, "