/
json.go
106 lines (91 loc) · 2.76 KB
/
json.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Copyright 2022 Juan Pablo Tosso and the OWASP Coraza contributors
// SPDX-License-Identifier: Apache-2.0
package bodyprocessors
import (
"io"
"strconv"
"strings"
"github.com/tidwall/gjson"
"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
)
type jsonBodyProcessor struct{}
var _ plugintypes.BodyProcessor = &jsonBodyProcessor{}
func (js *jsonBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.TransactionVariables, _ plugintypes.BodyProcessorOptions) error {
col := v.ArgsPost()
data, err := readJSON(reader)
if err != nil {
return err
}
for key, value := range data {
col.SetIndex(key, 0, value)
}
return nil
}
func (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, v plugintypes.TransactionVariables, _ plugintypes.BodyProcessorOptions) error {
col := v.ResponseArgs()
data, err := readJSON(reader)
if err != nil {
return err
}
for key, value := range data {
col.SetIndex(key, 0, value)
}
return nil
}
func readJSON(reader io.Reader) (map[string]string, error) {
s := strings.Builder{}
_, err := io.Copy(&s, reader)
if err != nil {
return nil, err
}
json := gjson.Parse(s.String())
res := make(map[string]string)
key := []byte("json")
readItems(json, key, res)
return res, nil
}
// Transform JSON to a map[string]string
// Example input: {"data": {"name": "John", "age": 30}, "items": [1,2,3]}
// Example output: map[string]string{"json.data.name": "John", "json.data.age": "30", "json.items.0": "1", "json.items.1": "2", "json.items.2": "3"}
// Example input: [{"data": {"name": "John", "age": 30}, "items": [1,2,3]}]
// Example output: map[string]string{"json.0.data.name": "John", "json.0.data.age": "30", "json.0.items.0": "1", "json.0.items.1": "2", "json.0.items.2": "3"}
// TODO add some anti DOS protection
func readItems(json gjson.Result, objKey []byte, res map[string]string) {
arrayLen := 0
json.ForEach(func(key, value gjson.Result) bool {
// Avoid string concatenation to maintain a single buffer for key aggregation.
prevParentLength := len(objKey)
objKey = append(objKey, '.')
if key.Type == gjson.String {
objKey = append(objKey, key.Str...)
} else {
objKey = strconv.AppendInt(objKey, int64(key.Num), 10)
arrayLen++
}
var val string
switch value.Type {
case gjson.JSON:
readItems(value, objKey, res)
objKey = objKey[:prevParentLength]
return true
case gjson.String:
val = value.Str
case gjson.Null:
val = ""
default:
// For all other types, raw JSON is what we need
val = value.Raw
}
res[string(objKey)] = val
objKey = objKey[:prevParentLength]
return true
})
if arrayLen > 0 {
res[string(objKey)] = strconv.Itoa(arrayLen)
}
}
func init() {
RegisterBodyProcessor("json", func() plugintypes.BodyProcessor {
return &jsonBodyProcessor{}
})
}