尝试创建一个递归遍历结构的函数,并根据某个标记修改字符串的任何字段。
与之合作反思非常繁琐。第一次使用它并遇到麻烦。
我从我的一行代码中得到了恐慌:
恐慌:反映:非结构类型的字段
恐慌来自这条线:
tf := vf.Type().Field(i)
我试图获取类型字段,以便从中获取标记。
这是完整的功能:
func Sanitize(s interface{}) error {
v := reflect.ValueOf(s)
// It's a pointer struct, convert to the value that it points to.
if v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
// Not a struct, return an error.
if v.Kind() != reflect.Struct {
return &InvalidSanitizerError{Type: reflect.TypeOf(s)}
}
for i := 0; i < v.NumField(); i++ {
vf := v.Field(i)
if vf.Kind() == reflect.Struct {
// Recurse.
err := Sanitize(v.Field(i).Interface())
if err != nil {
return err
}
// Move onto the next field.
continue
}
if vf.Kind() == reflect.String {
tf := vf.Type().Field(i) // <-- TROUBLE MAKER
// Get the field tag value
tag := tf.Tag.Get("sanitize")
// Skip if tag is not defined or ignored
if tag == "" || tag == "-" {
continue
}
shouldSanitize, err := strconv.ParseBool(tag)
if err != nil {
return err
}
if shouldSanitize && vf.CanSet() {
vf.SetString(policy.Sanitize(vf.String()))
}
}
}
return nil
}
这是如何使用该功能的一个例子:
type User struct {
Name string `sanitize:"true"`
Bio *Bio
}
type Bio struct {
Text string `sanitize:"true"`
}
func main() {
user := &User{
Name: "Lansana<script>alert('rekt');</script>",
Bio: &Bio{
Text: "Hello world</script>alert('rekt');</script>",
},
}
if err := Sanitize(user); err != nil {
panic(err)
}
fmt.Println(user.Name) // Lansana
fmt.Println(user.Bio.Text) // Hello world
}
非常感谢任何有关恐慌的见解。
答案 0 :(得分:1)
在整天工作之后,这是我最终提出的解决方案,以递归方式遍历结构并修改具有特定标记的所有字符串值的值。
Sanitize
函数只允许指向结构的指针,但嵌套结构可以是指针或值;这没关系。
我的问题中的示例将使用下面的函数,它会通过我的所有测试。
func Sanitize(s interface{}) error {
if s == nil {
return nil
}
val := reflect.ValueOf(s)
// If it's an interface or a pointer, unwrap it.
if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Struct {
val = val.Elem()
} else {
return &InvalidStructError{message: "s must be a struct"}
}
valNumFields := val.NumField()
for i := 0; i < valNumFields; i++ {
field := val.Field(i)
fieldKind := field.Kind()
// Check if it's a pointer to a struct.
if fieldKind == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
if field.CanInterface() {
// Recurse using an interface of the field.
err := Sanitize(field.Interface())
if err != nil {
return err
}
}
// Move onto the next field.
continue
}
// Check if it's a struct value.
if fieldKind == reflect.Struct {
if field.CanAddr() && field.Addr().CanInterface() {
// Recurse using an interface of the pointer value of the field.
err := Sanitize(field.Addr().Interface())
if err != nil {
return err
}
}
// Move onto the next field.
continue
}
// Check if it's a string or a pointer to a string.
if fieldKind == reflect.String || (fieldKind == reflect.Ptr && field.Elem().Kind() == reflect.String) {
typeField := val.Type().Field(i)
// Get the field tag value.
tag := typeField.Tag.Get("sanitize")
// Skip if tag is not defined or ignored.
if tag == "" || tag == "-" {
continue
}
// Check if the tag is allowed.
if tag != "true" && tag != "false" {
return &InvalidTagError{message: "tag must be either 'true' or 'false'."}
}
// Parse it to a bool.
shouldSanitize, err := strconv.ParseBool(tag)
if err != nil {
return err
} else if !shouldSanitize {
continue
}
// Set the string value to the sanitized string if it's allowed.
// It should always be allowed at this point.
if field.CanSet() {
field.SetString(policy.Sanitize(field.String()))
}
continue
}
}
return nil
}