递归结构反射错误:panic:reflect:非结构类型的字段

时间:2018-03-24 01:41:31

标签: go reflection

尝试创建一个递归遍历结构的函数,并根据某个标记修改字符串的任何字段。

与之合作反思非常繁琐。第一次使用它并遇到麻烦。

我从我的一行代码中得到了恐慌:

  

恐慌:反映:非结构类型的字段

恐慌来自这条线:

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
}

非常感谢任何有关恐慌的见解。

1 个答案:

答案 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
}