-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathanonymizer.go
More file actions
242 lines (199 loc) · 5.53 KB
/
anonymizer.go
File metadata and controls
242 lines (199 loc) · 5.53 KB
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package anonymizer
import (
"io"
"reflect"
"github.com/vxcontrol/cloud/anonymizer/patterns"
)
type Anonymizer interface {
Anonymize(any) error
ReplaceString(string) string
ReplaceBytes([]byte) []byte
WrapReader(io.Reader) io.Reader
}
type anonymizer struct {
Replacer
}
func NewAnonymizer(pts []patterns.Pattern) (Anonymizer, error) {
patterns, err := patterns.LoadPatterns(patterns.PatternListTypeAll)
if err != nil {
return nil, err
}
patterns.Patterns = append(patterns.Patterns, pts...)
replacer, err := NewReplacer(patterns.Regexes(), patterns.Names())
if err != nil {
return nil, err
}
return &anonymizer{Replacer: replacer}, nil
}
func (a *anonymizer) Anonymize(v any) error {
rv := reflect.ValueOf(v)
if !isMutable(rv) {
return ErrObjectImmutable
}
// use visited map to prevent infinite recursion
visited := make(map[uintptr]bool)
return a.anonymizeValue(rv, visited)
}
// anonymizeValue recursively anonymizes a reflect.Value with cycle detection
func (a *anonymizer) anonymizeValue(v reflect.Value, visited map[uintptr]bool) error {
if !v.IsValid() {
return nil
}
// check for cycles in pointer types
if v.Kind() == reflect.Pointer && !v.IsNil() {
ptr := v.Pointer()
if visited[ptr] {
return nil // already visited, skip to prevent cycles
}
visited[ptr] = true
}
switch v.Kind() {
case reflect.Pointer:
if v.IsNil() {
return nil
}
// for pointer to string, anonymize the string value directly
elem := v.Elem()
if elem.Kind() == reflect.String && elem.CanSet() {
original := elem.String()
anonymized := a.ReplaceString(original)
elem.SetString(anonymized)
return nil
}
// for other types, recurse
return a.anonymizeValue(elem, visited)
case reflect.Interface:
if v.IsNil() {
return nil
}
elem := v.Elem()
// if the interface contains a struct value, we need special handling
if elem.Kind() == reflect.Struct {
// create a new struct value that can be modified
newStruct := reflect.New(elem.Type()).Elem()
newStruct.Set(elem)
if err := a.anonymizeValue(newStruct.Addr(), visited); err != nil {
return err
}
// set the modified struct back to the interface
v.Set(newStruct)
return nil
}
// for other types, try to modify in place if possible
if elem.CanSet() {
return a.anonymizeValue(elem, visited)
}
// if we can't modify in place, create a new value
newVal := reflect.New(elem.Type()).Elem()
newVal.Set(elem)
if err := a.anonymizeValue(newVal, visited); err != nil {
return err
}
v.Set(newVal)
return nil
case reflect.Map:
return a.anonymizeMap(v, visited)
case reflect.Slice, reflect.Array:
return a.anonymizeSlice(v, visited)
case reflect.Struct:
return a.anonymizeStruct(v, visited)
case reflect.String:
if v.CanSet() {
original := v.String()
anonymized := a.ReplaceString(original)
v.SetString(anonymized)
}
return nil
default:
// for other types (numbers, bools, etc.) do nothing
return nil
}
}
// anonymizeMap anonymizes all values in a map with cycle detection
func (a *anonymizer) anonymizeMap(v reflect.Value, visited map[uintptr]bool) error {
if v.IsNil() {
return nil
}
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
val := iter.Value()
// handle different value types appropriately
switch val.Kind() {
case reflect.Pointer:
// for pointers, work directly with the original pointer
if err := a.anonymizeValue(val, visited); err != nil {
return err
}
// no need to set back to map - we modified the pointed-to value
case reflect.Struct:
// for struct values in map, create a copy and work with its address
newVal := reflect.New(val.Type()).Elem()
newVal.Set(val)
if err := a.anonymizeValue(newVal.Addr(), visited); err != nil {
return err
}
v.SetMapIndex(key, newVal)
default:
// for other types (strings, primitives, slices, maps), create a copy
newVal := reflect.New(val.Type()).Elem()
newVal.Set(val)
if err := a.anonymizeValue(newVal, visited); err != nil {
return err
}
v.SetMapIndex(key, newVal)
}
}
return nil
}
// anonymizeSlice anonymizes all elements in a slice or array with cycle detection
func (a *anonymizer) anonymizeSlice(v reflect.Value, visited map[uintptr]bool) error {
for i := 0; i < v.Len(); i++ {
elem := v.Index(i)
if err := a.anonymizeValue(elem, visited); err != nil {
return err
}
}
return nil
}
// anonymizeStruct anonymizes struct fields based on their tags and types with cycle detection
func (a *anonymizer) anonymizeStruct(v reflect.Value, visited map[uintptr]bool) error {
structType := v.Type()
// get struct information from cache or parse it
info, err := getStructInfo(structType)
if err != nil {
return err
}
// iterate through all fields
for _, field := range info.Fields {
// skip fields marked with skip tag
if field.Skip {
continue
}
// get field value using index path
fieldValue := v
for _, idx := range field.Index {
if fieldValue.Kind() == reflect.Pointer {
if fieldValue.IsNil() {
// skip nil pointers - don't create new objects
fieldValue = reflect.Value{}
break
}
fieldValue = fieldValue.Elem()
}
if !fieldValue.IsValid() || idx >= fieldValue.NumField() {
// field index out of range or invalid, skip
fieldValue = reflect.Value{}
break
}
fieldValue = fieldValue.Field(idx)
}
// anonymize the field value only if it's valid and not marked as skip
if fieldValue.IsValid() {
if err := a.anonymizeValue(fieldValue, visited); err != nil {
return err
}
}
}
return nil
}