// Copyright 2024 Patial Tech (Ankit Patial). // // This file is part of code.patial.tech/go/appcore, which is MIT licensed. // See http://opensource.org/licenses/MIT package email import ( "bytes" "html/template" txt "text/template" ) // RenderHTMLTemplate renders HTML email templates with automatic XSS protection. // // SECURITY WARNING: // - DO NOT pass untrusted user input as layout or content parameters // - Only use trusted, predefined template strings // - User data should ONLY be passed via the data map parameter // - The html/template package auto-escapes data to prevent XSS // - Template injection attacks are possible if template strings come from user input // // Example of SECURE usage: // // const layoutTemplate = "{{template \"content\" .}}" // const contentTemplate = "

Hello {{.Name}}

" // data := map[string]any{"Name": userInput} // Safe - will be escaped // html, err := RenderHTMLTemplate(layoutTemplate, contentTemplate, data) func RenderHTMLTemplate(layout, content string, data map[string]any) (string, error) { // layout tpl, err := template.New("layout").Parse(layout) if err != nil { return "", err } // content _, err = tpl.New("content").Parse(content) if err != nil { return "", err } // execute layout + content template and RenderHTML data buf := new(bytes.Buffer) err = tpl.ExecuteTemplate(buf, "layout", data) if err != nil { return "", err } return buf.String(), nil } // RenderTxtTemplate renders plain text email templates WITHOUT escaping. // // SECURITY WARNING: // - DO NOT use this function for HTML content (use RenderHTMLTemplate instead) // - DO NOT pass untrusted user input as the content parameter // - Only use trusted, predefined template strings // - User data should ONLY be passed via the data map parameter // - text/template does NOT provide XSS protection - no auto-escaping // - Template injection attacks are possible if content comes from user input // // Example of SECURE usage: // // const textTemplate = "Hello {{.Name}}, your order #{{.OrderID}} is confirmed." // data := map[string]any{"Name": userName, "OrderID": orderID} // text, err := RenderTxtTemplate(textTemplate, data) func RenderTxtTemplate(content string, data map[string]any) (string, error) { tpl, err := txt.New("content").Parse(content) if err != nil { return "", err } buf := new(bytes.Buffer) err = tpl.ExecuteTemplate(buf, "content", data) if err != nil { return "", err } return buf.String(), nil }