/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.common.xss;

import cn.hutool.core.util.StrUtil;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

/**
 * `XssWrapper`类继承自`HttpServletRequestWrapper`，它的主要作用是对`HttpServletRequest`中的各类可能包含用户输入数据的部分进行跨站脚本攻击（XSS，Cross-Site Scripting）过滤，
 * 通过重写一些关键方法，在获取请求参数、请求属性以及请求头信息等数据时，对其中可能存在的恶意脚本代码（如 `<script>` 标签及其包含的JavaScript代码等特殊字符）进行清理和过滤，
 * 确保传递到后续业务逻辑中的数据是安全的，避免了因用户输入恶意数据而导致的XSS攻击风险，保障Web应用的安全性。
 *
 * @author lgh
 */
public class XssWrapper extends HttpServletRequestWrapper {
    /**
     * 构造函数，用于创建一个`XssWrapper`对象，它接收一个`HttpServletRequest`对象作为参数，并将其传递给父类的构造函数，实现对原始请求对象的包装。
     * 这样后续在重写的方法中就可以通过调用父类的相应方法来获取原始的请求数据，然后在此基础上进行XSS过滤处理。
     *
     * @param request The request to wrap，要包装的`HttpServletRequest`对象，不能为`null`，如果传入`null`会抛出`IllegalArgumentException`异常，因为包装一个`null`请求是没有意义的。
     * @throws IllegalArgumentException 如果传入的请求对象是`null`，则会抛出此异常，以确保包装的请求对象是有效的，能够进行后续的处理操作。
     */
    public XssWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * 对数组参数进行特殊字符过滤的方法，重写了`HttpServletRequestWrapper`中的`getParameterValues`方法。
     * 这个方法主要用于获取指定名称的参数值数组（例如复选框等多选表单元素提交的数据就是以数组形式存在的参数），它的逻辑如下：
     * 1. 首先通过调用父类的`getParameterValues`方法获取原始的参数值数组，这个原始数组就是客户端发送过来的未经处理的数据。
     * 2. 如果获取到的原始参数值数组为`null`，说明没有对应的参数值，直接返回`null`，不进行额外处理。
     * 3. 如果获取到了参数值数组，获取其长度，然后创建一个同样长度的新字符串数组`encodedValues`，用于存储经过XSS过滤后的参数值。
     * 4. 通过循环遍历原始参数值数组中的每个元素，对每个元素调用`cleanXss`方法进行XSS过滤处理，将过滤后的结果存入新创建的`encodedValues`数组中。
     * 5. 最后返回经过XSS过滤后的参数值数组，使得后续业务逻辑获取到的是安全的参数值，避免了恶意脚本代码通过数组形式的参数进入系统。
     *
     * @param name 参数的名称，用于指定要获取哪个参数的数组值，例如在表单中有多个同名的复选框，通过这个名称就可以获取到它们提交的值数组。
     * @return 返回经过XSS过滤后的参数值数组，如果原始参数值数组为`null`，则返回`null`，否则返回过滤后新的参数值数组。
     */
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = cleanXss(values[i]);
        }
        return encodedValues;
    }

    /**
     * 对参数中特殊字符进行过滤的方法，重写了`HttpServletRequestWrapper`中的`getParameter`方法。
     * 这个方法用于获取指定名称的单个参数值（例如文本框、单选框等表单元素提交的单个值参数），其逻辑如下：
     * 1. 首先通过调用父类的`getParameter`方法获取原始的参数值，这个值就是客户端发送过来的未经处理的数据。
     * 2. 如果获取到的原始参数值为空字符串（通过`StrUtil.isBlank`方法判断，它会判断字符串是否为`null`或者长度为0），说明没有实际的参数值，直接返回该空字符串，不进行额外处理。
     * 3. 如果获取到的参数值不为空，调用`cleanXss`方法对其进行XSS过滤处理，然后返回过滤后的参数值，确保单个参数值的安全性，防止恶意脚本代码通过单个参数进入系统。
     *
     * @param name 参数的名称，用于指定要获取哪个参数的值，例如在表单中通过元素的`name`属性指定的名称来获取对应的值。
     * @return 返回经过XSS过滤后的参数值，如果原始参数值为空字符串，则返回该空字符串，否则返回过滤后的安全参数值。
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (StrUtil.isBlank(value)) {
            return value;
        }
        return cleanXss(value);
    }

    /**
     * 获取`attribute`，特殊字符过滤的方法，重写了`HttpServletRequestWrapper`中的`getAttribute`方法。
     * 这个方法用于获取请求对象中设置的属性值（在Web应用中，可能在不同的处理环节会通过`setAttribute`方法设置一些属性，用于在请求处理过程中传递数据等），其逻辑如下：
     * 1. 首先通过调用父类的`getAttribute`方法获取原始的属性值。
     * 2. 如果获取到的属性值是字符串类型并且不为空字符串（通过`instanceof`判断是否为字符串以及`StrUtil.isNotBlank`判断是否为空字符串），说明可能存在用户设置的文本数据，需要进行XSS过滤，
     *    调用`cleanXss`方法对其进行过滤处理，然后返回过滤后的属性值；如果获取到的属性值不是字符串类型或者为空字符串，则直接返回原始的属性值，不进行额外处理。
     * 这样就确保了从请求对象中获取的属性值在是字符串且有实际内容的情况下是经过安全过滤的，避免了属性值中携带恶意脚本代码而引发安全问题。
     *
     * @param name 属性的名称，用于指定要获取哪个属性的值，例如在Servlet或者过滤器中通过`setAttribute`方法设置属性时指定的名称，通过这个名称就可以获取对应的属性值。
     * @return 返回经过XSS过滤后的属性值，如果原始属性值不是字符串类型或者为空字符串，则返回原始属性值。
     */
    @Override
    public Object getAttribute(String name) {
        Object value = super.getAttribute(name);
        if (value instanceof String && StrUtil.isNotBlank((String) value)) {
            return cleanXss((String) value);
        }
        return value;
    }

    /**
     * 对请求头部进行特殊字符过滤的方法，重写了`HttpServletRequestWrapper`中的`getHeader`方法。
     * 这个方法用于获取请求头中的信息（例如`User-Agent`、`Referer`等请求头字段的值），其逻辑如下：
     * 1. 首先通过调用父类的`getHeader`方法获取原始的请求头值，这个值就是客户端发送过来的未经处理的数据。
     * 2. 如果获取到的原始请求头值为空字符串（通过`StrUtil.isBlank`方法判断），说明没有对应的请求头信息，直接返回该空字符串，不进行额外处理。
     * 3. 如果获取到的请求头值不为空，调用`cleanXss`方法对其进行XSS过滤处理，然后返回过滤后的请求头值，确保请求头信息的安全性，防止恶意脚本代码通过请求头进入系统，
     *    因为在一些情况下，恶意攻击者可能会尝试通过篡改请求头来发起攻击，进行这样的过滤可以增强系统的安全性。
     *
     * @param name 请求头的名称，用于指定要获取哪个请求头的值，例如`"Content-Type"`、`"Authorization"`等常见的请求头名称，通过这个名称就可以获取对应的请求头字段的值。
     * @return 返回经过XSS过滤后的请求头值，如果原始请求头值为空字符串，则返回该空字符串，否则返回过滤后的安全请求头值。
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (StrUtil.isBlank(value)) {
        return value;
        }
        return cleanXss(value);
    }

    /**
     * 私有方法，用于实际执行对字符串进行XSS过滤的操作，它调用了`XssUtil.clean`方法（这里推测`XssUtil`类是实现具体XSS过滤逻辑的工具类）来对传入的字符串进行过滤，
     * 将字符串中可能存在的恶意脚本代码等特殊字符进行清理，返回过滤后的安全字符串，供上述各个重写的方法使用，以实现对不同类型请求相关数据的XSS过滤功能。
     *
     * @param value 需要进行XSS过滤的字符串，可能是参数值、属性值或者请求头值等各种从请求对象中获取到的字符串数据。
     * @return 返回经过XSS过滤后的安全字符串。
     */
    private String cleanXss(String value) {
        return XssUtil.clean(value);
    }
}