Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions dsl.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ var (

funcSignatureRegex = regexp.MustCompile(`(\w+)\s*\((?:([\w\d,\s]+)\s+([.\w\d{}&*]+))?\)([\s.\w\d{}&*]+)?`)

// unicodeEscapeRegex matches runs of \uXXXX and \UXXXXXXXX escape sequences
unicodeEscapeRegex = regexp.MustCompile(`(?:\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8})+`)

// ErrParsingArg is error when parsing value of argument
// Use With Caution: Nuclei ignores this error in extractors(ref: https://github.com/projectdiscovery/nuclei/issues/3950)
ErrParsingArg = errkit.New("error parsing argument value")
Expand Down Expand Up @@ -557,6 +560,27 @@ func init() {
decodeString, err := hex.DecodeString(toString(args[0]))
return string(decodeString), err
}))
MustAddFunction(NewWithPositionalArgs("unicode_encode", 1, true, func(args ...interface{}) (interface{}, error) {
var result strings.Builder
for _, r := range toString(args[0]) {
if r > 0xFFFF {
fmt.Fprintf(&result, `\U%08x`, r)
} else {
fmt.Fprintf(&result, `\u%04x`, r)
}
}
return result.String(), nil
}))
MustAddFunction(NewWithPositionalArgs("unicode_decode", 1, true, func(args ...interface{}) (interface{}, error) {
// decode any \uXXXX / \UXXXXXXXX escape runs and leave the surrounding text untouched
result := unicodeEscapeRegex.ReplaceAllStringFunc(toString(args[0]), func(match string) string {
if unquoted, err := strconv.Unquote(`"` + match + `"`); err == nil {
return unquoted
}
return match
})
return result, nil
}))
MustAddFunction(NewWithPositionalArgs("hmac", 3, true, func(args ...interface{}) (interface{}, error) {
hashAlgorithm := args[0]
data := args[1].(string)
Expand Down
45 changes: 45 additions & 0 deletions dsl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,49 @@ func TestDSLURLEncodeDecode(t *testing.T) {
})
}

func TestDSLUnicodeEncodeDecode(t *testing.T) {
t.Run("round trip", func(t *testing.T) {
testCases := []struct {
name string
input string
}{
{"ascii", "ok"},
{"chinese", "百度一下"},
{"mixed", "hello 世界 123"},
{"emoji astral plane", "🚀✨"},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
encoded, err := DefaultHelperFunctions["unicode_encode"](tc.input)
require.NoError(t, err, "unicode_encode should not error")

decoded, err := DefaultHelperFunctions["unicode_decode"](encoded)
require.NoError(t, err, "unicode_decode should not error")
require.Equal(t, tc.input, decoded, "unicode_decode should reverse unicode_encode")
})
}
})

t.Run("encode output", func(t *testing.T) {
encoded, err := DefaultHelperFunctions["unicode_encode"]("ok")
require.NoError(t, err)
require.Equal(t, `\u006f\u006b`, encoded)
})

t.Run("decode mixed text and escapes", func(t *testing.T) {
decoded, err := DefaultHelperFunctions["unicode_decode"](`hello \u4e16\u754c`)
require.NoError(t, err)
require.Equal(t, "hello 世界", decoded)
})

t.Run("decode without escapes is unchanged", func(t *testing.T) {
decoded, err := DefaultHelperFunctions["unicode_decode"]("plain text")
require.NoError(t, err)
require.Equal(t, "plain text", decoded)
})
}

func TestDSLTimeComparison(t *testing.T) {
compiled, err := govaluate.NewEvaluableExpressionWithFunctions("unixtime() > not_after", DefaultHelperFunctions)
require.Nil(t, err, "could not compare time")
Expand Down Expand Up @@ -324,6 +367,8 @@ func TestGetPrintableDslFunctionSignatures(t *testing.T) {
trim_right(arg1, arg2 interface{}) interface{}
trim_space(arg1 interface{}) interface{}
trim_suffix(arg1, arg2 interface{}) interface{}
unicode_decode(arg1 interface{}) interface{}
unicode_encode(arg1 interface{}) interface{}
uniq(elements ...interface{}) []interface{}
uniq(input number) string
uniq(input string) string
Expand Down
Loading