diff --git a/internal/transformations/hex_decode.go b/internal/transformations/hex_decode.go new file mode 100644 index 000000000..3e0aff391 --- /dev/null +++ b/internal/transformations/hex_decode.go @@ -0,0 +1,16 @@ +package transformations + +import ( + "encoding/hex" + + "github.com/corazawaf/coraza/v3/internal/strings" +) + +func hexDecode(data string) (string, bool, error) { + dst, err := hex.DecodeString(data) + if err != nil { + return "", false, err + } + + return strings.WrapUnsafe(dst), true, nil +} diff --git a/internal/transformations/hex_decode_test.go b/internal/transformations/hex_decode_test.go new file mode 100644 index 000000000..9b145968b --- /dev/null +++ b/internal/transformations/hex_decode_test.go @@ -0,0 +1,98 @@ +package transformations + +import ( + "testing" +) + +func TestHexDecode(t *testing.T) { + tests := []struct { + name string + input string + expectedOutput string + expectedValid bool + expectError bool + }{ + { + name: "valid hexadecimal string", + input: "48656c6c6f", + expectedOutput: "Hello", + expectedValid: true, + expectError: false, + }, + { + name: "odd length", + input: "48656c6c6f7", + expectedOutput: "", + expectedValid: false, + expectError: true, + }, + { + name: "invalid with non hex characters", + input: "YyYy", + expectedOutput: "", + expectedValid: false, + expectError: true, + }, + { + name: "invalid with extra characters", + input: "123G", + expectedOutput: "", + expectedValid: false, + expectError: true, + }, + { + name: "empty input", + input: "", + expectedOutput: "", + expectedValid: true, + expectError: false, + }, + { + name: "uppercase hex string", + input: "48454C4C4F", + expectedOutput: "HELLO", + expectedValid: true, + expectError: false, + }, + { + name: "mixed case", + input: "48454c4C4f", + expectedOutput: "HELLO", + expectedValid: true, + expectError: false, + }, + { + name: "special characters", + input: "21402324255E262A28", + expectedOutput: "!@#$%^&*(", + expectedValid: true, + expectError: false, + }, + { + name: "odd length with invalid character", + input: "48656c6c6fZ", + expectedOutput: "", + expectedValid: false, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + output, valid, err := hexDecode(tt.input) + + if (err != nil) != tt.expectError { + t.Errorf("hexDecode(%q): expected error=%v, got error=%v", tt.input, tt.expectError, err) + } + + if output != tt.expectedOutput { + t.Errorf("hexDecode(%q): expected output=%q, got output=%q", tt.input, tt.expectedOutput, output) + } + + if valid != tt.expectedValid { + t.Errorf("hexDecode(%q): expected valid=%v, got valid=%v", tt.input, tt.expectedValid, valid) + } + }) + } +} diff --git a/internal/transformations/testdata/hexDecode.json b/internal/transformations/testdata/hexDecode.json index 664fbd812..99345b222 100644 --- a/internal/transformations/testdata/hexDecode.json +++ b/internal/transformations/testdata/hexDecode.json @@ -1,44 +1,37 @@ [ - { - "ret" : 1, - "input" : "", - "type" : "tfn", - "name" : "hexDecode", - "output" : "" - }, - { - "output" : "TestCase", - "ret" : 1, - "name" : "hexDecode", - "input" : "5465737443617365", - "type" : "tfn" - }, - { - "type" : "tfn", - "input" : "546573740043617365", - "name" : "hexDecode", - "ret" : 1, - "output" : "Test\\u0000Case" - }, - { - "output" : "\\x01#Eg\\x89\\x0a#\\x01#Eg\\x89\\x0a", - "type" : "tfn", - "input" : "01234567890a0z01234567890a", - "name" : "hexDecode", - "ret" : 1 - }, - { - "type" : "tfn", - "name" : "hexDecode", - "input" : "01234567890az", - "output" : "\\x01#Eg\\x89\\x0a", - "ret" : 1 - }, - { - "type" : "tfn", - "name" : "hexDecode", - "input" : "01234567890a0", - "output" : "\\x01#Eg\\x89\\x0a", - "ret" : 1 - } + { + "ret": 1, + "input": "", + "type": "tfn", + "name": "hexDecode", + "output": "" + }, + { + "output": "TestCase", + "ret": 1, + "name": "hexDecode", + "input": "5465737443617365", + "type": "tfn" + }, + { + "type": "tfn", + "input": "546573740043617365", + "name": "hexDecode", + "ret": 1, + "output": "Test\\u0000Case" + }, + { + "type": "tfn", + "name": "invalidCharacter", + "input": "01234567890z", + "output": "", + "ret": 0 + }, + { + "type": "tfn", + "name": "invalidLen", + "input": "54657374004", + "output": "", + "ret": 0 + } ] diff --git a/internal/transformations/transformations.go b/internal/transformations/transformations.go index 69aad6a1b..78cdda258 100644 --- a/internal/transformations/transformations.go +++ b/internal/transformations/transformations.go @@ -35,6 +35,7 @@ func init() { Register("compressWhitespace", compressWhitespace) Register("cssDecode", cssDecode) Register("escapeSeqDecode", escapeSeqDecode) + Register("hexDecode", hexDecode) Register("hexEncode", hexEncode) Register("htmlEntityDecode", htmlEntityDecode) Register("jsDecode", jsDecode)