From 81b9284351b0e651dbf8c47ed024e3fa1bfc3ba7 Mon Sep 17 00:00:00 2001 From: Dwi Siswanto Date: Wed, 17 Sep 2025 16:58:27 +0700 Subject: [PATCH 1/3] refactor: adds `optionalConvertAllChars` for `html_escape` Signed-off-by: Dwi Siswanto --- README.md | 30 +++++++++++++++++++++++------ dsl.go | 22 +++++++++++++++++---- dsl_test.go | 55 ++++++++++++++++++++++++++++------------------------- go.mod | 4 ++++ util.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index bf3f192..0921183 100644 --- a/README.md +++ b/README.md @@ -70,23 +70,27 @@ func main() { | contains(input, substring interface{}) bool | Verifies if a string contains a substring | `contains("Hello", "lo")` | `true` | | contains_all(input interface{}, substrings ...string) bool | Verifies if any input contains all of the substrings | `contains_all("Hello everyone", "lo", "every")` | `true` | | contains_any(input interface{}, substrings ...string) bool | Verifies if an input contains any of substrings | `contains_any("Hello everyone", "abc", "llo")` | `true` | +| cookie_unsign(s string) string | Unsigned cookies by removing HMAC signature | `cookie_unsign("value.signature")` | `value` | +| count(str, substr string) int | Counts non-overlapping instances of substr in str | `count("hello world", "l")` | `3` | | date_time(dateTimeFormat string, optionalUnixTime interface{}) string | Returns the formatted date time using simplified or `go` style layout for the current or the given unix time | `date_time("%Y-%M-%D %H:%m")`
`date_time("%Y-%M-%D %H:%m", 1654870680)`
`date_time("2006-01-02 15:04", unix_time())` | `2022-06-10 14:18` | | dec_to_hex(number number | string) string | Transforms the input number into hexadecimal format | `dec_to_hex(7001)"` | `1b59` | | deflate(input string) string | Compresses the input using DEFLATE | `deflate("Hello")` | `"\xf2\x48\xcd\xc9\xc9\x07\x04\x00\x00\xff\xff"` | | ends_with(str string, suffix ...string) bool | Checks if the string ends with any of the provided substrings | `ends_with("Hello", "lo")` | `true` | +| equals_any(s interface{}, subs ...interface{}) bool | Checks if the input equals any of the provided values | `equals_any("test", "not", "test")` | `true` | | generate_java_gadget(gadget, cmd, encoding interface{}) string | Generates a Java Deserialization Gadget | `generate_java_gadget("dns", "{{interactsh-url}}", "base64")` | `rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QAAHQAAHEAfgAFdAAFcHh0ACpjYWhnMmZiaW41NjRvMGJ0MHRzMDhycDdlZXBwYjkxNDUub2FzdC5mdW54` | | generate_jwt(json, algorithm, signature, unixMaxAge) []byte | Generates a JSON Web Token (JWT) using the claims provided in a JSON string, the signature, and the specified algorithm | `generate_jwt("{\"name\":\"John Doe\",\"foo\":\"bar\"}", "HS256", "hello-world")` | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYW1lIjoiSm9obiBEb2UifQ.EsrL8lIcYJR_Ns-JuhF3VCllCP7xwbpMCCfHin_WT6U` | | gzip(input string) string | Compresses the input using GZip | `base64(gzip("Hello"))` | `+H4sIAAAAAAAA//JIzcnJBwQAAP//gonR9wUAAAA=` | -| gzip_decode(input string) string | Decompresses the input using GZip | `gzip_decode(hex_decode("1f8b08000000000000fff248cdc9c907040000ffff8289d1f705000000"))` | `Hello` | +| gzip_decode(data string, optionalReadLimit int) string | Decompresses the input using GZip with optional read limit | `gzip_decode(hex_decode("1f8b08000000000000fff248cdc9c907040000ffff8289d1f705000000"))` | `Hello` | | gzip_mtime(input string) float64 | Extracts the modification time (MTIME) from a GZIP header and returns it as Unix timestamp. Input should be raw gzip data. | `gzip_mtime(hex_decode("1f8b08000000000000fff248cdc9c907040000ffff8289d1f705000000"))` | `0` | | hex_decode(input interface{}) []byte | Hex decodes the given input | `hex_decode("6161")` | `aa` | | hex_encode(input interface{}) string | Hex encodes the given input | `hex_encode("aa")` | `6161` | | hex_to_dec(hexNumber number | string) float64 | Transforms the input hexadecimal number into decimal format | `hex_to_dec("ff")`
`hex_to_dec("0xff")` | `255` | | hmac(algorithm, data, secret) string | hmac function that accepts a hashing function type with data and secret | `hmac("sha1", "test", "scrt")` | `8856b111056d946d5c6c92a21b43c233596623c6` | -| html_escape(input interface{}) string | HTML escapes the given input | `html_escape("test")` | `<body>test</body>` | +| html_escape(s string, optionalConvertAllChars bool) string | HTML escapes the given input. When optionalConvertAllChars is true, converts all characters to numeric entities | `html_escape("test")`
`html_escape("test", true)` | `<body>test</body>`
`<body>test</body>` | | html_unescape(input interface{}) string | HTML un-escapes the given input | `html_unescape("<body>test</body>")` | `test` | | index(slice, index) interface{} | Select item at index from slice or string (zero based) | `index("test",0)` | `t` | -| inflate(input string) string | Decompresses the input using DEFLATE | `inflate(hex_decode("f348cdc9c90700"))` | `Hello` | +| inflate(data string, optionalReadLimit int) string | Decompresses the input using DEFLATE with optional read limit | `inflate(hex_decode("f348cdc9c90700"))` | `Hello` | +| ip_format(ip, format interface{}) interface{} | Formats an IP address according to the specified format | `ip_format("192.168.1.1", "1")` | `192.168.1.1` | | join(separator string, elements ...interface{}) string | Joins the given elements using the specified separator | `join("_", 123, "hello", "world")` | `123_hello_world` | | jarm(hostport string) string | Calculate the jarm hash for the host:port combination | `jarm("127.0.0.1:443")` | `29d29d00029d29d00041d41d000000ad9bf51cc3f5a1e29eecb81d0c7b06eb` | | json_minify(json) string | Minifies a JSON string by removing unnecessary whitespace | `json_minify("{ \"name\": \"John Doe\", \"foo\": \"bar\" }")` | `{"foo":"bar","name":"John Doe"}` | @@ -98,7 +102,9 @@ func main() { | md5(input interface{}) string | Calculates the MD5 (Message Digest) hash of the input | `md5("Hello")` | `8b1a9953c4611296a827abf8c47804d7` | | mmh3(input interface{}) string | Calculates the MMH3 (MurmurHash3) hash of an input | `mmh3("Hello")` | `316307400` | | oct_to_dec(octalNumber number | string) float64 | Transforms the input octal number into a decimal format | `oct_to_dec("0o1234567")`
`oct_to_dec(1234567)` | `342391` | +| padding(input, padding, length, position interface{}) interface{} | Adds padding to input string until it reaches desired length at specified position | `padding("test", "0", 10, "left")` | `000000test` | | print_debug(args ...interface{}) | Prints the value of a given input or expression. Used for debugging. | `print_debug(1+2, "Hello")` | `3 Hello` | +| public_ip() string | Returns the public IP address of the current machine | `public_ip()` | `203.0.113.1` | | rand_base(length uint, optionalCharSet string) string | Generates a random sequence of given length string from an optional charset (defaults to letters and numbers) | `rand_base(5, "abc")` | `caccb` | | rand_char(optionalCharSet string) string | Generates a random character from an optional character set (defaults to letters and numbers) | `rand_char("abc")` | `a` | | rand_int(optionalMin, optionalMax uint) int | Generates a random integer between the given optional limits (defaults to 0 - MaxInt32) | `rand_int(1, 10)` | `6` | @@ -107,15 +113,26 @@ func main() { | rand_text_alphanumeric(length uint, optionalBadChars string) string | Generates a random alphanumeric string, of given length without the optional cutset characters | `rand_text_alphanumeric(10, "ab12")` | `NthI0IiY8r` | | rand_text_numeric(length uint, optionalBadNumbers string) string | Generates a random numeric string of given length without the optional set of undesired numbers | `rand_text_numeric(10, 123)` | `0654087985` | | regex(pattern, input string) bool | Tests the given regular expression against the input string | `regex("H([a-z]+)o", "Hello")` | `true` | +| regex_all(pattern string, inputs ...string) bool | Tests if the regular expression matches all provided inputs | `regex_all("^[a-z]+$", "hello", "world")` | `true` | +| regex_any(pattern string, inputs ...string) bool | Tests if the regular expression matches any of the provided inputs | `regex_any("^[0-9]+$", "hello", "123")` | `true` | | remove_bad_chars(input, cutset interface{}) string | Removes the desired characters from the input | `remove_bad_chars("abcd", "bc")` | `ad` | | repeat(str string, count uint) string | Repeats the input string the given amount of times | `repeat("../", 5)` | `../../../../../` | | replace(str, old, new string) string | Replaces a given substring in the given input | `replace("Hello", "He", "Ha")` | `Hallo` | | replace_regex(source, regex, replacement string) string | Replaces substrings matching the given regular expression in the input | `replace_regex("He123llo", "(\\d+)", "")` | `Hello` | | reverse(input string) string | Reverses the given input | `reverse("abc")` | `cba` | +| rsa_encrypt(data, publicKeyPEM interface{}) interface{} | RSA encrypts data with a public key in PEM format | `rsa_encrypt("hello", publicKeyPEM)` | `encrypted_data` | | sha1(input interface{}) string | Calculates the SHA1 (Secure Hash 1) hash of the input | `sha1("Hello")` | `f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0` | | sha256(input interface{}) string | Calculates the SHA256 (Secure Hash 256) hash of the input | `sha256("Hello")` | `185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969` | +| sha512(input interface{}) string | Calculates the SHA512 (Secure Hash 512) hash of the input | `sha512("Hello")` | `3615f80c9d293ed7402687f94b22d58e529b8cc7916f8fac7fddf7fbd5af4cf777d3d795a7a00a16bf7e7f3fb9561ee9baae480da9fe7a18769e71886b03f315` | +| sort(input string) string
sort(input number) string
sort(elements ...interface{}) []interface{} | Sorts the characters in a string, digits in a number, or elements in a list | `sort("dcba")`
`sort(4321)`
`sort("world", "hello")` | `abcd`
`1234`
`["hello", "world"]` | +| split(input string, n int) []string
split(input string, separator string, optionalChunkSize) []string | Splits a string by a separator or into chunks of specified size | `split("a,b,c", ",")`
`split("hello", "", 2)` | `["a", "b", "c"]`
`["he", "ll", "o"]` | | starts_with(str string, prefix ...string) bool | Checks if the string starts with any of the provided substrings | `starts_with("Hello", "He")` | `true` | +| substr(str string, start int, optionalEnd int) | Extracts a substring from the input string | `substr("hello", 1, 3)` | `el` | +| to_bool(input interface{}) bool | Converts the input to a boolean value | `to_bool("true")`
`to_bool(1)`
`to_bool("")` | `true`
`true`
`false` | | to_lower(input string) string | Transforms the input into lowercase characters | `to_lower("HELLO")` | `hello` | +| to_number(input interface{}) interface{} | Converts the input to a number | `to_number("123")` | `123` | +| to_string(input interface{}) interface{} | Converts the input to a string | `to_string(123)` | `"123"` | +| to_title(s, optionalLang string) string | Converts string to title case with optional language parameter | `to_title("hello world")` | `Hello World` | | to_unix_time(input string, layout string) int | Parses a string date time using default or user given layouts, then returns its Unix timestamp | `to_unix_time("2022-01-13T16:30:10+00:00")`
`to_unix_time("2022-01-13 16:30:10")`
`to_unix_time("13-01-2022 16:30:10". "02-01-2006 15:04:05")` | `1642091410` | | to_upper(input string) string | Transforms the input into uppercase characters | `to_upper("hello")` | `HELLO` | | trim(input, cutset string) string | Returns a slice of the input with all leading and trailing Unicode code points contained in cutset removed | `trim("aaaHelloddd", "ad")` | `Hello` | @@ -124,15 +141,16 @@ func main() { | trim_right(input, cutset string) string | Returns a string, with all trailing Unicode code points contained in cutset removed | `trim_right("aaaHelloddd", "ad")` | `aaaHello` | | trim_space(input string) string | Returns a string, with all leading and trailing white space removed, as defined by Unicode | `trim_space(" Hello ")` | `"Hello"` | | trim_suffix(input, suffix string) string | Returns input without the provided trailing suffix string | `trim_suffix("aaHelloaa", "aa")` | `aaHello` | +| uniq(input string) string
uniq(input number) string
uniq(elements ...interface{}) []interface{} | Removes duplicate characters from a string, digits from a number, or elements from a list | `uniq("aabbcc")`
`uniq(112233)`
`uniq("b", "a", "b")` | `abc`
`123`
`["b", "a"]` | | unix_time(optionalSeconds uint) float64 | Returns the current Unix time (number of seconds elapsed since January 1, 1970 UTC) with the added optional seconds | `unix_time(10)` | `1639568278` | | unpack(format string, sequence string/bytes) {}interface | Returns the result of python binary unpack for the first operand in the sequence (endianess+operand) | `unpack('>I', '\xac\xd7\t\xd0')` | `-272646673` | | url_decode(input string) string | URL decodes the input string | `url_decode("https:%2F%2Fprojectdiscovery.io%3Ftest=1")` | `https://projectdiscovery.io?test=1` | -| url_encode(input string) string | URL encodes the input string | `url_encode("https://projectdiscovery.io/test?a=1")` | `https%3A%2F%2Fprojectdiscovery.io%2Ftest%3Fa%3D1` | +| url_encode(s string, optionalEncodeAllSpecialChars bool) string | URL encodes the input string. When optionalEncodeAllSpecialChars is true, encodes all special characters | `url_encode("https://projectdiscovery.io/test?a=1")`
`url_encode("test@example.com", true)` | `https%3A%2F%2Fprojectdiscovery.io%2Ftest%3Fa%3D1`
`test%40example%2Ecom` | | wait_for(seconds uint) | Pauses the execution for the given amount of seconds | `wait_for(10)` | `true` | | xor(sequences ...strings/bytes) | Perform xor on sequences of same length | `xor('abc','def')` | `50705` in hex | +| zip(file_entry string, content string, ...) []byte | Creates a ZIP archive with alternating file entries and content | `zip("file1.txt", "content1", "file2.txt", "content2")` | `zip_archive_bytes` | | zlib(input string) string | Compresses the input using Zlib | `base64(zlib("Hello"))` | `eJzySM3JyQcEAAD//wWMAfU=` | -| zlib_decode(input string) string | Decompresses the input using Zlib | `zlib_decode(hex_decode("789cf248cdc9c907040000ffff058c01f5"))` | `Hello` | -| padding(input string, padding string,length int) string | Adds padding char to input string until it reaches desired length | `padding("A","b",3)` | `Abb` | +| zlib_decode(data string, optionalReadLimit int) string | Decompresses the input using Zlib with optional read limit | `zlib_decode(hex_decode("789cf248cdc9c907040000ffff058c01f5"))` | `Hello` | **Supported encodings:** diff --git a/dsl.go b/dsl.go index dc73673..a4567d1 100644 --- a/dsl.go +++ b/dsl.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" "hash" - "html" "io" "math" "net" @@ -49,6 +48,7 @@ import ( "github.com/projectdiscovery/mapcidr" jarm "github.com/projectdiscovery/utils/crypto/jarm" "github.com/projectdiscovery/utils/errkit" + "github.com/projectdiscovery/utils/html" maputils "github.com/projectdiscovery/utils/maps" randint "github.com/projectdiscovery/utils/rand" stringsutil "github.com/projectdiscovery/utils/strings" @@ -527,9 +527,20 @@ func init() { h.Write([]byte(data)) return hex.EncodeToString(h.Sum(nil)), nil })) - MustAddFunction(NewWithPositionalArgs("html_escape", 1, true, func(args ...interface{}) (interface{}, error) { - return html.EscapeString(toString(args[0])), nil - })) + MustAddFunction(NewWithSingleSignature("html_escape", + "(s string, optionalConvertAllChars bool) string", + true, + func(args ...interface{}) (interface{}, error) { + s := toString(args[0]) + if len(args) > 1 { + convertAllChars := toBool(args[1]) + if convertAllChars { + return strToNumEntities(s), nil + } + } + + return html.EscapeString(s), nil + })) MustAddFunction(NewWithPositionalArgs("html_unescape", 1, true, func(args ...interface{}) (interface{}, error) { return html.UnescapeString(toString(args[0])), nil })) @@ -1082,6 +1093,9 @@ func init() { MustAddFunction(NewWithPositionalArgs("to_string", 1, true, func(args ...interface{}) (interface{}, error) { return toString(args[0]), nil })) + MustAddFunction(NewWithPositionalArgs("to_bool", 1, true, func(args ...interface{}) (interface{}, error) { + return toBool(args[0]), nil + })) MustAddFunction(NewWithPositionalArgs("dec_to_hex", 1, true, func(args ...interface{}) (interface{}, error) { if number, ok := args[0].(float64); ok { hexNum := strconv.FormatInt(int64(number), 16) diff --git a/dsl_test.go b/dsl_test.go index 7a02ff4..48704a7 100644 --- a/dsl_test.go +++ b/dsl_test.go @@ -263,7 +263,7 @@ func TestGetPrintableDslFunctionSignatures(t *testing.T) { hex_encode(arg1 interface{}) interface{} hex_to_dec(arg1 interface{}) interface{} hmac(arg1, arg2, arg3 interface{}) interface{} - html_escape(arg1 interface{}) interface{} + html_escape(s string, optionalConvertAllChars bool) string html_unescape(arg1 interface{}) interface{} index(arg1, arg2 interface{}) interface{} inflate(data string, optionalReadLimit int) string @@ -349,32 +349,35 @@ func TestDslExpressions(t *testing.T) { `base64(1234)`: "MTIzNA==", `base64_py("Hello")`: "SGVsbG8=\n", `hex_encode("aa")`: "6161", - `html_escape("test")`: "<body>test</body>", + `html_escape("test")`: "<body>test</body>", + `html_escape("test", true)`: "<body>test</body>", `html_unescape("<body>test</body>")`: "test", - `md5("Hello")`: "8b1a9953c4611296a827abf8c47804d7", - `md5(1234)`: "81dc9bdb52d04dc20036dbd8313ed055", - `mmh3("Hello")`: "316307400", - `remove_bad_chars("abcd", "bc")`: "ad", - `replace("Hello", "He", "Ha")`: "Hallo", - `concat("Hello", 123, "world")`: "Hello123world", - `join("_", "Hello", 123, "world")`: "Hello_123_world", - `repeat("a", 5)`: "aaaaa", - `repeat("a", "5")`: "aaaaa", - `repeat("../", "5")`: "../../../../../", - `repeat(5, 5)`: "55555", - `replace_regex("He123llo", "(\\d+)", "")`: "Hello", - `reverse("abc")`: "cba", - `sha1("Hello")`: "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0", - `sha256("Hello")`: "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969", - `sha512("Hello")`: "3615f80c9d293ed7402687f94b22d58e529b8cc7916f8fac7fddf7fbd5af4cf777d3d795a7a00a16bf7e7f3fb9561ee9baae480da9fe7a18769e71886b03f315", - `to_lower("HELLO")`: "hello", - `to_upper("hello")`: "HELLO", - `trim("aaaHelloddd", "ad")`: "Hello", - `trim_left("aaaHelloddd", "ad")`: "Helloddd", - `trim_prefix("aaHelloaa", "aa")`: "Helloaa", - `trim_right("aaaHelloddd", "ad")`: "aaaHello", - `trim_space(" Hello ")`: "Hello", - `trim_suffix("aaHelloaa", "aa")`: "aaHello", + `html_unescape("<body>test</body>")`: "test", + `html_unescape("<body>test</body>")`: "test", + `md5("Hello")`: "8b1a9953c4611296a827abf8c47804d7", + `md5(1234)`: "81dc9bdb52d04dc20036dbd8313ed055", + `mmh3("Hello")`: "316307400", + `remove_bad_chars("abcd", "bc")`: "ad", + `replace("Hello", "He", "Ha")`: "Hallo", + `concat("Hello", 123, "world")`: "Hello123world", + `join("_", "Hello", 123, "world")`: "Hello_123_world", + `repeat("a", 5)`: "aaaaa", + `repeat("a", "5")`: "aaaaa", + `repeat("../", "5")`: "../../../../../", + `repeat(5, 5)`: "55555", + `replace_regex("He123llo", "(\\d+)", "")`: "Hello", + `reverse("abc")`: "cba", + `sha1("Hello")`: "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0", + `sha256("Hello")`: "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969", + `sha512("Hello")`: "3615f80c9d293ed7402687f94b22d58e529b8cc7916f8fac7fddf7fbd5af4cf777d3d795a7a00a16bf7e7f3fb9561ee9baae480da9fe7a18769e71886b03f315", + `to_lower("HELLO")`: "hello", + `to_upper("hello")`: "HELLO", + `trim("aaaHelloddd", "ad")`: "Hello", + `trim_left("aaaHelloddd", "ad")`: "Helloddd", + `trim_prefix("aaHelloaa", "aa")`: "Helloaa", + `trim_right("aaaHelloddd", "ad")`: "aaaHello", + `trim_space(" Hello ")`: "Hello", + `trim_suffix("aaHelloaa", "aa")`: "aaHello", `url_decode("https:%2F%2Fprojectdiscovery.io%3Ftest=1")`: "https://projectdiscovery.io?test=1", `url_encode("https://projectdiscovery.io/test?a=1")`: "https%3A%2F%2Fprojectdiscovery.io%2Ftest%3Fa%3D1", `gzip("Hello")`: "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xf2H\xcd\xc9\xc9\a\x04\x00\x00\xff\xff\x82\x89\xd1\xf7\x05\x00\x00\x00", diff --git a/go.mod b/go.mod index f23218c..f4347f3 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,10 @@ require ( golang.org/x/text v0.24.0 ) +replace ( + github.com/projectdiscovery/utils => /home/dw1/Development/PD/utils +) + require ( github.com/STARRY-S/zip v0.2.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect diff --git a/util.go b/util.go index 77d2307..75bfb1d 100644 --- a/util.go +++ b/util.go @@ -13,6 +13,7 @@ import ( "github.com/kataras/jwt" "github.com/pkg/errors" + "github.com/projectdiscovery/utils/html" randint "github.com/projectdiscovery/utils/rand" ) @@ -78,6 +79,34 @@ func toString(data interface{}) string { } } +// toBool converts an interface to boolean in a quick way +func toBool(data interface{}) bool { + switch s := data.(type) { + case nil: + return false + case bool: + return s + case string: + if s == "" { + return false + } + // Try parsing as boolean first + if b, err := strconv.ParseBool(s); err == nil { + return b + } + // Non-empty strings are considered true + return true + case int, int8, int16, int32, int64: + return s != 0 + case uint, uint8, uint16, uint32, uint64: + return s != 0 + case float32, float64: + return s != 0 + default: + return false + } +} + func insertInto(s string, interval int, sep rune) string { var buffer bytes.Buffer before := interval - 1 @@ -252,3 +281,29 @@ func aggregate(values []string) string { } return builder.String() } + +// strToNumEntities applies HTML escaping, then converts non-HTML-entity +// characters to numeric entities. +func strToNumEntities(s string) string { + escaped := html.EscapeString(s) + + var result strings.Builder + i := 0 + for i < len(escaped) { + if escaped[i] == '&' { + semicolonPos := strings.Index(escaped[i:], ";") + if semicolonPos != -1 { + entityEnd := i + semicolonPos + 1 + result.WriteString(escaped[i:entityEnd]) + i = entityEnd + continue + } + } + + r := rune(escaped[i]) + result.WriteString(fmt.Sprintf("&#%d;", int(r))) + i++ + } + + return result.String() +} From 777c0f1f12ddb5287fd1abef658290c1012dbd64 Mon Sep 17 00:00:00 2001 From: Dwi Siswanto Date: Thu, 2 Oct 2025 20:20:09 +0700 Subject: [PATCH 2/3] chore: rm replace directive Signed-off-by: Dwi Siswanto --- go.mod | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.mod b/go.mod index f4347f3..f23218c 100644 --- a/go.mod +++ b/go.mod @@ -22,10 +22,6 @@ require ( golang.org/x/text v0.24.0 ) -replace ( - github.com/projectdiscovery/utils => /home/dw1/Development/PD/utils -) - require ( github.com/STARRY-S/zip v0.2.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect From 8ccb248f261523813039eede9eef67b46598d87d Mon Sep 17 00:00:00 2001 From: Dwi Siswanto Date: Thu, 2 Oct 2025 21:02:03 +0700 Subject: [PATCH 3/3] fix: refine toBool util & test Signed-off-by: Dwi Siswanto --- dsl_test.go | 8 ++++++++ util.go | 49 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/dsl_test.go b/dsl_test.go index 48704a7..c42a156 100644 --- a/dsl_test.go +++ b/dsl_test.go @@ -309,6 +309,7 @@ func TestGetPrintableDslFunctionSignatures(t *testing.T) { split(input string, separator string, optionalChunkSize) []string starts_with(str string, prefix ...string) bool substr(str string, start int, optionalEnd int) + to_bool(arg1 interface{}) interface{} to_lower(arg1 interface{}) interface{} to_number(arg1 interface{}) interface{} to_string(arg1 interface{}) interface{} @@ -370,6 +371,13 @@ func TestDslExpressions(t *testing.T) { `sha1("Hello")`: "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0", `sha256("Hello")`: "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969", `sha512("Hello")`: "3615f80c9d293ed7402687f94b22d58e529b8cc7916f8fac7fddf7fbd5af4cf777d3d795a7a00a16bf7e7f3fb9561ee9baae480da9fe7a18769e71886b03f315", + `to_bool(1)`: true, + `to_bool("1")`: true, + `to_bool("true")`: true, + `to_bool("TrUe")`: false, + `to_bool("0")`: false, + `to_bool(0)`: false, + `to_bool("x")`: false, `to_lower("HELLO")`: "hello", `to_upper("hello")`: "HELLO", `trim("aaaHelloddd", "ad")`: "Hello", diff --git a/util.go b/util.go index 75bfb1d..9ecc8e9 100644 --- a/util.go +++ b/util.go @@ -87,21 +87,52 @@ func toBool(data interface{}) bool { case bool: return s case string: + s = strings.TrimSpace(s) + if s == "true" { + return true + } + if s == "" { return false } - // Try parsing as boolean first + if b, err := strconv.ParseBool(s); err == nil { return b } - // Non-empty strings are considered true - return true - case int, int8, int16, int32, int64: - return s != 0 - case uint, uint8, uint16, uint32, uint64: - return s != 0 - case float32, float64: - return s != 0 + + if f, err := strconv.ParseFloat(s, 64); err == nil { + return f == 1 + } + + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + return i == 1 + } + + return false + case int: + return s == 1 + case int8: + return s == 1 + case int16: + return s == 1 + case int32: + return s == 1 + case int64: + return s == 1 + case uint: + return s == 1 + case uint8: + return s == 1 + case uint16: + return s == 1 + case uint32: + return s == 1 + case uint64: + return s == 1 + case float32: + return s == 1 + case float64: + return s == 1 default: return false }