package main
import (
"html/template"
"net/http"
"github.com/microcosm-cc/bluemonday"
)
// Safe: Structured data for templates
type PageData struct {
Name string
Message string
Theme string
}
type ProfileData struct {
Bio string
Signature string
RichBio template.HTML // Only for sanitized content
}
// Safe: Pre-compiled templates with auto-escaping
var pageTemplate = template.Must(template.New("page").Parse(`
<html>
<body>
<h1>Welcome {{.Name}}!</h1>
<div class="{{.Theme}}">{{.Message}}</div>
<footer>Powered by Go</footer>
</body>
</html>
`))
var profileTemplate = template.Must(template.New("profile").Parse(`
<div class="profile">
<div class="bio">{{.Bio}}</div>
<div class="signature">{{.Signature}}</div>
{{if .RichBio}}<div class="rich-bio">{{.RichBio}}</div>{{end}}
</div>
`))
// HTML sanitization policy
var htmlPolicy = bluemonday.UGCPolicy()
func safeHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
message := r.URL.Query().Get("message")
theme := r.URL.Query().Get("theme")
// Validate theme against whitelist
allowedThemes := map[string]bool{
"light": true, "dark": true, "blue": true,
}
if !allowedThemes[theme] {
theme = "light" // Default safe value
}
// Safe: Structured data with auto-escaping
data := PageData{
Name: name, // Auto-escaped by template
Message: message, // Auto-escaped by template
Theme: theme, // Validated against whitelist
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
pageTemplate.Execute(w, data)
}
func safeProfileHandler(w http.ResponseWriter, r *http.Request) {
bio := r.PostFormValue("bio")
signature := r.PostFormValue("signature")
richContent := r.PostFormValue("rich_content")
// Option 1: Plain text (recommended)
data := ProfileData{
Bio: bio, // Auto-escaped
Signature: signature, // Auto-escaped
}
// Option 2: If rich content is needed, sanitize first
if richContent != "" {
safeHTML := htmlPolicy.Sanitize(richContent)
data.RichBio = template.HTML(safeHTML) // Safe after sanitization
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
profileTemplate.Execute(w, data)
}
// Alternative: JSON API approach
func apiHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
message := r.URL.Query().Get("message")
w.Header().Set("Content-Type", "application/json")
// Safe: JSON encoding handles escaping
jsonResponse := fmt.Sprintf(`{"name":"%s","message":"%s"}`,
template.JSEscapeString(name),
template.JSEscapeString(message))
fmt.Fprint(w, jsonResponse)
}