Przeglądaj źródła

EventManager: escape email body when content type is text/html

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 8 miesięcy temu
rodzic
commit
1c48e51384

+ 46 - 34
internal/common/eventmanager.go

@@ -21,6 +21,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"html"
 	"io"
 	"mime"
 	"mime/multipart"
@@ -775,14 +776,18 @@ func (p *EventParams) getRetentionReportsAsMailAttachment() (*mail.File, error)
 	}, nil
 }
 
-func (*EventParams) getStringReplacement(val string, jsonEscaped bool) string {
-	if jsonEscaped {
+func (*EventParams) getStringReplacement(val string, escapeMode int) string {
+	switch escapeMode {
+	case 1:
 		return util.JSONEscape(val)
+	case 2:
+		return html.EscapeString(val)
+	default:
+		return val
 	}
-	return val
 }
 
-func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []string {
+func (p *EventParams) getStringReplacements(addObjectData bool, escapeMode int) []string {
 	var dateTimeString string
 	if Config.TZ == "local" {
 		dateTimeString = p.Timestamp.Local().Format(dateTimeMillisFormat)
@@ -796,23 +801,23 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
 	minute := dateTimeString[14:16]
 
 	replacements := []string{
-		"{{.Name}}", p.getStringReplacement(p.Name, jsonEscaped),
+		"{{.Name}}", p.getStringReplacement(p.Name, escapeMode),
 		"{{.Event}}", p.Event,
 		"{{.Status}}", fmt.Sprintf("%d", p.Status),
-		"{{.VirtualPath}}", p.getStringReplacement(p.VirtualPath, jsonEscaped),
-		"{{.EscapedVirtualPath}}", p.getStringReplacement(url.QueryEscape(p.VirtualPath), jsonEscaped),
-		"{{.FsPath}}", p.getStringReplacement(p.FsPath, jsonEscaped),
-		"{{.VirtualTargetPath}}", p.getStringReplacement(p.VirtualTargetPath, jsonEscaped),
-		"{{.FsTargetPath}}", p.getStringReplacement(p.FsTargetPath, jsonEscaped),
-		"{{.ObjectName}}", p.getStringReplacement(p.ObjectName, jsonEscaped),
-		"{{.ObjectBaseName}}", p.getStringReplacement(strings.TrimSuffix(p.ObjectName, p.Extension), jsonEscaped),
+		"{{.VirtualPath}}", p.getStringReplacement(p.VirtualPath, escapeMode),
+		"{{.EscapedVirtualPath}}", p.getStringReplacement(url.QueryEscape(p.VirtualPath), escapeMode),
+		"{{.FsPath}}", p.getStringReplacement(p.FsPath, escapeMode),
+		"{{.VirtualTargetPath}}", p.getStringReplacement(p.VirtualTargetPath, escapeMode),
+		"{{.FsTargetPath}}", p.getStringReplacement(p.FsTargetPath, escapeMode),
+		"{{.ObjectName}}", p.getStringReplacement(p.ObjectName, escapeMode),
+		"{{.ObjectBaseName}}", p.getStringReplacement(strings.TrimSuffix(p.ObjectName, p.Extension), escapeMode),
 		"{{.ObjectType}}", p.ObjectType,
 		"{{.FileSize}}", strconv.FormatInt(p.FileSize, 10),
 		"{{.Elapsed}}", strconv.FormatInt(p.Elapsed, 10),
 		"{{.Protocol}}", p.Protocol,
 		"{{.IP}}", p.IP,
-		"{{.Role}}", p.getStringReplacement(p.Role, jsonEscaped),
-		"{{.Email}}", p.getStringReplacement(p.Email, jsonEscaped),
+		"{{.Role}}", p.getStringReplacement(p.Role, escapeMode),
+		"{{.Email}}", p.getStringReplacement(p.Email, escapeMode),
 		"{{.Timestamp}}", strconv.FormatInt(p.Timestamp.UnixNano(), 10),
 		"{{.DateTime}}", dateTimeString,
 		"{{.Year}}", year,
@@ -821,18 +826,18 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
 		"{{.Hour}}", hour,
 		"{{.Minute}}", minute,
 		"{{.StatusString}}", p.getStatusString(),
-		"{{.UID}}", p.getStringReplacement(p.UID, jsonEscaped),
-		"{{.Ext}}", p.getStringReplacement(p.Extension, jsonEscaped),
+		"{{.UID}}", p.getStringReplacement(p.UID, escapeMode),
+		"{{.Ext}}", p.getStringReplacement(p.Extension, escapeMode),
 	}
 	if p.VirtualPath != "" {
-		replacements = append(replacements, "{{.VirtualDirPath}}", p.getStringReplacement(path.Dir(p.VirtualPath), jsonEscaped))
+		replacements = append(replacements, "{{.VirtualDirPath}}", p.getStringReplacement(path.Dir(p.VirtualPath), escapeMode))
 	}
 	if p.VirtualTargetPath != "" {
-		replacements = append(replacements, "{{.VirtualTargetDirPath}}", p.getStringReplacement(path.Dir(p.VirtualTargetPath), jsonEscaped))
-		replacements = append(replacements, "{{.TargetName}}", p.getStringReplacement(path.Base(p.VirtualTargetPath), jsonEscaped))
+		replacements = append(replacements, "{{.VirtualTargetDirPath}}", p.getStringReplacement(path.Dir(p.VirtualTargetPath), escapeMode))
+		replacements = append(replacements, "{{.TargetName}}", p.getStringReplacement(path.Base(p.VirtualTargetPath), escapeMode))
 	}
 	if len(p.errors) > 0 {
-		replacements = append(replacements, "{{.ErrorString}}", p.getStringReplacement(strings.Join(p.errors, ", "), jsonEscaped))
+		replacements = append(replacements, "{{.ErrorString}}", p.getStringReplacement(strings.Join(p.errors, ", "), escapeMode))
 	} else {
 		replacements = append(replacements, "{{.ErrorString}}", "")
 	}
@@ -842,13 +847,13 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
 		data, err := p.Object.RenderAsJSON(p.Event != operationDelete)
 		if err == nil {
 			dataString := util.BytesToString(data)
-			replacements[len(replacements)-3] = p.getStringReplacement(dataString, false)
-			replacements[len(replacements)-1] = p.getStringReplacement(dataString, true)
+			replacements[len(replacements)-3] = p.getStringReplacement(dataString, 0)
+			replacements[len(replacements)-1] = p.getStringReplacement(dataString, 1)
 		}
 	}
 	if p.IDPCustomFields != nil {
 		for k, v := range *p.IDPCustomFields {
-			replacements = append(replacements, fmt.Sprintf("{{.IDPField%s}}", k), p.getStringReplacement(v, jsonEscaped))
+			replacements = append(replacements, fmt.Sprintf("{{.IDPField%s}}", k), p.getStringReplacement(v, escapeMode))
 		}
 	}
 	replacements = append(replacements, "{{.Metadata}}", "{}")
@@ -857,8 +862,8 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
 		data, err := json.Marshal(p.Metadata)
 		if err == nil {
 			dataString := util.BytesToString(data)
-			replacements[len(replacements)-3] = p.getStringReplacement(dataString, false)
-			replacements[len(replacements)-1] = p.getStringReplacement(dataString, true)
+			replacements[len(replacements)-3] = p.getStringReplacement(dataString, 0)
+			replacements[len(replacements)-1] = p.getStringReplacement(dataString, 1)
 		}
 	}
 	return replacements
@@ -1314,7 +1319,7 @@ func writeHTTPPart(m *multipart.Writer, part dataprovider.HTTPPart, h textproto.
 	if part.Body != "" {
 		cType := h.Get("Content-Type")
 		if strings.Contains(strings.ToLower(cType), "application/json") {
-			replacements := params.getStringReplacements(addObjectData, true)
+			replacements := params.getStringReplacements(addObjectData, 1)
 			jsonReplacer := strings.NewReplacer(replacements...)
 			_, err = partWriter.Write(util.StringToBytes(replaceWithReplacer(part.Body, jsonReplacer)))
 		} else {
@@ -1362,7 +1367,7 @@ func getHTTPRuleActionBody(c *dataprovider.EventActionHTTPConfig, replacer *stri
 			return bytes.NewBuffer(data), "", nil
 		}
 		if c.HasJSONBody() {
-			replacements := params.getStringReplacements(addObjectData, true)
+			replacements := params.getStringReplacements(addObjectData, 1)
 			jsonReplacer := strings.NewReplacer(replacements...)
 			return bytes.NewBufferString(replaceWithReplacer(c.Body, jsonReplacer)), "", nil
 		}
@@ -1450,7 +1455,7 @@ func executeHTTPRuleAction(c dataprovider.EventActionHTTPConfig, params *EventPa
 		addObjectData = c.HasObjectData()
 	}
 
-	replacements := params.getStringReplacements(addObjectData, false)
+	replacements := params.getStringReplacements(addObjectData, 0)
 	replacer := strings.NewReplacer(replacements...)
 	endpoint, err := getHTTPRuleActionEndpoint(&c, replacer)
 	if err != nil {
@@ -1521,7 +1526,7 @@ func executeCommandRuleAction(c dataprovider.EventActionCommandConfig, params *E
 			}
 		}
 	}
-	replacements := params.getStringReplacements(addObjectData, false)
+	replacements := params.getStringReplacements(addObjectData, 0)
 	replacer := strings.NewReplacer(replacements...)
 
 	args := make([]string, 0, len(c.Args))
@@ -1576,9 +1581,16 @@ func executeEmailRuleAction(c dataprovider.EventActionEmailConfig, params *Event
 			addObjectData = true
 		}
 	}
-	replacements := params.getStringReplacements(addObjectData, false)
+	replacements := params.getStringReplacements(addObjectData, 0)
 	replacer := strings.NewReplacer(replacements...)
-	body := replaceWithReplacer(c.Body, replacer)
+	var body string
+	if c.ContentType == 1 {
+		replacements := params.getStringReplacements(addObjectData, 2)
+		bodyReplacer := strings.NewReplacer(replacements...)
+		body = replaceWithReplacer(c.Body, bodyReplacer)
+	} else {
+		body = replaceWithReplacer(c.Body, replacer)
+	}
 	subject := replaceWithReplacer(c.Subject, replacer)
 	recipients := getEmailAddressesWithReplacer(c.Recipients, replacer)
 	bcc := getEmailAddressesWithReplacer(c.Bcc, replacer)
@@ -2150,7 +2162,7 @@ func executeFsRuleAction(c dataprovider.EventActionFilesystemConfig, conditions
 	params *EventParams,
 ) error {
 	addObjectData := false
-	replacements := params.getStringReplacements(addObjectData, false)
+	replacements := params.getStringReplacements(addObjectData, 0)
 	replacer := strings.NewReplacer(replacements...)
 	switch c.Type {
 	case dataprovider.FilesystemActionRename:
@@ -2550,7 +2562,7 @@ func executeAdminCheckAction(c *dataprovider.EventActionIDPAccountCheck, params
 		return nil, err
 	}
 
-	replacements := params.getStringReplacements(false, true)
+	replacements := params.getStringReplacements(false, 1)
 	replacer := strings.NewReplacer(replacements...)
 	data := replaceWithReplacer(c.TemplateAdmin, replacer)
 
@@ -2620,7 +2632,7 @@ func executeUserCheckAction(c *dataprovider.EventActionIDPAccountCheck, params *
 	if err != nil && !errors.Is(err, util.ErrNotFound) {
 		return nil, err
 	}
-	replacements := params.getStringReplacements(false, true)
+	replacements := params.getStringReplacements(false, 1)
 	replacer := strings.NewReplacer(replacements...)
 	data := replaceWithReplacer(c.TemplateUser, replacer)
 

+ 3 - 3
internal/common/eventmanager_test.go

@@ -808,7 +808,7 @@ func TestDateTimePlaceholder(t *testing.T) {
 	params := EventParams{
 		Timestamp: dateTime,
 	}
-	replacements := params.getStringReplacements(false, false)
+	replacements := params.getStringReplacements(false, 0)
 	r := strings.NewReplacer(replacements...)
 	res := r.Replace("{{.DateTime}}")
 	assert.Equal(t, dateTime.UTC().Format(dateTimeMillisFormat), res)
@@ -816,7 +816,7 @@ func TestDateTimePlaceholder(t *testing.T) {
 	assert.Equal(t, dateTime.UTC().Format(dateTimeMillisFormat)[:16], res)
 
 	Config.TZ = "local"
-	replacements = params.getStringReplacements(false, false)
+	replacements = params.getStringReplacements(false, 0)
 	r = strings.NewReplacer(replacements...)
 	res = r.Replace("{{.DateTime}}")
 	assert.Equal(t, dateTime.Local().Format(dateTimeMillisFormat), res)
@@ -2331,7 +2331,7 @@ func TestMetadataReplacement(t *testing.T) {
 			"key": "value",
 		},
 	}
-	replacements := params.getStringReplacements(false, false)
+	replacements := params.getStringReplacements(false, 0)
 	replacer := strings.NewReplacer(replacements...)
 	reader, _, err := getHTTPRuleActionBody(&dataprovider.EventActionHTTPConfig{Body: "{{.Metadata}} {{.MetadataString}}"}, replacer, nil, dataprovider.User{}, params, false)
 	require.NoError(t, err)

+ 5 - 2
internal/common/protocol_test.go

@@ -6966,7 +6966,7 @@ func TestEventRuleRenameEvent(t *testing.T) {
 				Recipients:  []string{"[email protected]"},
 				Subject:     `"{{.Event}}" from "{{.Name}}"`,
 				ContentType: 1,
-				Body:        `<p>Fs path {{.FsPath}}, Target path "{{.VirtualTargetDirPath}}/{{.TargetName}}", size: {{.FileSize}}</p>`,
+				Body:        `<p>Fs path {{.FsPath}}, Name: {{.Name}}, Target path "{{.VirtualTargetDirPath}}/{{.TargetName}}", size: {{.FileSize}}</p>`,
 			},
 		},
 	}
@@ -6991,7 +6991,9 @@ func TestEventRuleRenameEvent(t *testing.T) {
 	rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
 	assert.NoError(t, err)
 
-	user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
+	u := getTestUser()
+	u.Username = "test <html > chars"
+	user, _, err := httpdtest.AddUser(u, http.StatusCreated)
 	assert.NoError(t, err)
 	conn, client, err := getSftpClient(user)
 	if assert.NoError(t, err) {
@@ -7015,6 +7017,7 @@ func TestEventRuleRenameEvent(t *testing.T) {
 		assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "rename" from "%s"`, user.Username))
 		assert.Contains(t, email.Data, "Content-Type: text/html")
 		assert.Contains(t, email.Data, fmt.Sprintf("Target path %q", path.Join("/subdir", testFileName)))
+		assert.Contains(t, email.Data, "Name: test &lt;html &gt; chars,")
 	}
 
 	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)