package mailclient import ( "bytes" "crypto/rand" "encoding/json" "fmt" "golang.org/x/net/html" "html/template" "io" "gitea.pena/SQuiz/common/model" "strings" ) type LineWriter struct { w io.Writer length int } func NewLineWriter(w io.Writer, length int) *LineWriter { return &LineWriter{ w: w, length: length, } } func (r *LineWriter) Write(p []byte) (n int, err error) { for i := 0; i < len(p); i += r.length { end := i + r.length if end > len(p) { end = len(p) - 1 } var chunk []byte chunk = append(chunk, p[i:end]...) if len(p) >= end+r.length { chunk = append(chunk, []byte("\r\n")...) } addN, err := r.w.Write(chunk) if err != nil { return n, err } n += addN } return n, nil } func (r *LineWriter) WriteString(s string) (n int, err error) { p := []byte(s) return r.Write(p) } func (r *LineWriter) WriteFormatString(format string, a ...any) (n int, err error) { p := []byte(fmt.Sprintf(format, a...)) return r.Write(p) } func randomBoundary() string { var buf [30]byte _, err := io.ReadFull(rand.Reader, buf[:]) if err != nil { panic(err) } return fmt.Sprintf("%x", buf[:]) } var tmplFuncs = template.FuncMap{ "renderImage": RenderImage, } func RenderImage(content string) template.HTML { contents := strings.Split(content, "`,`") var builder strings.Builder for i, cnt := range contents { if i == 0 { cnt = strings.TrimPrefix(cnt, "`") } if i == len(contents)-1 { cnt = strings.TrimSuffix(cnt, "`") } var res model.ImageContent err := json.Unmarshal([]byte(cnt), &res) if err != nil { return SplitContent(content) } else { builder.WriteString(fmt.Sprintf("%s
", res.Description, res.Image)) } } return template.HTML(builder.String()) } func SplitContent(content string) template.HTML { parts := strings.Split(content, "|") if len(parts) == 2 { url := strings.TrimSpace(parts[0]) filename := strings.TrimSpace(parts[1]) return template.HTML(fmt.Sprintf(`%s`, url, filename)) } return template.HTML(content) } func sanitizeHTMLData(data EmailTemplateData) EmailTemplateData { sanitized := EmailTemplateData{ QuizConfig: stripHTMLResultInfo(data.QuizConfig), AnswerContent: stripHTMLResultContent(data.AnswerContent), AllAnswers: stripHTMLResultAnswers(data.AllAnswers), QuestionsMap: stripHTMLResultMap(data.QuestionsMap), AnswerTime: StripHTML(data.AnswerTime), QuizID: data.QuizID, } return sanitized } func stripHTMLResultInfo(input model.ResultInfo) model.ResultInfo { return model.ResultInfo{ When: StripHTML(input.When), Theme: StripHTML(input.Theme), Reply: StripHTML(input.Reply), ReplName: StripHTML(input.ReplName), } } func stripHTMLResultContent(input model.ResultContent) model.ResultContent { return model.ResultContent{ Text: StripHTML(input.Text), Name: StripHTML(input.Name), Email: StripHTML(input.Email), Phone: StripHTML(input.Phone), Address: StripHTML(input.Address), Telegram: StripHTML(input.Telegram), Wechat: StripHTML(input.Wechat), Viber: StripHTML(input.Viber), Vk: StripHTML(input.Vk), Skype: StripHTML(input.Skype), Whatsup: StripHTML(input.Whatsup), Messenger: StripHTML(input.Messenger), Custom: stripHTMLCustom(input.Custom), } } func stripHTMLResultAnswers(answers []model.ResultAnswer) []model.ResultAnswer { sanitized := make([]model.ResultAnswer, len(answers)) for i, j := range answers { sanitized[i] = model.ResultAnswer{ Content: StripHTML(j.Content), CreatedAt: j.CreatedAt, QuestionID: j.QuestionID, AnswerID: j.AnswerID, } } return sanitized } func stripHTMLResultMap(questionsMap map[uint64]string) map[uint64]string { sanitized := make(map[uint64]string) for i, j := range questionsMap { sanitized[i] = StripHTML(j) } return sanitized } func stripHTMLCustom(custom map[string]string) map[string]string { sanitized := make(map[string]string) for i, j := range custom { sanitized[i] = StripHTML(j) } return sanitized } func StripHTML(htmlString string) string { tokenizer := html.NewTokenizer(bytes.NewBufferString(htmlString)) var result bytes.Buffer for { tokenType := tokenizer.Next() switch tokenType { case html.ErrorToken: return strings.TrimSpace(result.String()) case html.TextToken: result.WriteString(tokenizer.Token().Data) result.WriteString("\n") case html.StartTagToken, html.EndTagToken: tagName, _ := tokenizer.TagName() if string(tagName) == "a" { _, attrVal, _ := tokenizer.TagAttr() result.WriteString(string(attrVal)) result.WriteString("\n") } } } }