Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
vertx-web/src/main/java/io/vertx/ext/web/impl/Utils.java
### 代码内容：
package io.vertx.ext.web.impl;

import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.regex.Pattern;

/**
 * @author Tim Fox
 * @author Paulo Lopes
 */
public class Utils {

  public static ClassLoader getClassLoader() {
    // try current thread
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    if (cl == null) {
      // try the current class
      cl = Utils.class.getClassLoader();
      if (cl == null) {
        // fall back to a well known object that should alwways exist
        cl = Object.class.getClassLoader();
      }
    }
    return cl;
  }

  private static final ZoneId ZONE_GMT = ZoneId.of("GMT");

  public static String formatRFC1123DateTime(final long time) {
    return DateTimeFormatter.RFC_1123_DATE_TIME.format(Instant.ofEpochMilli(time).atZone(ZONE_GMT));
  }

  public static long parseRFC1123DateTime(final String header) {
    try {
      return header == null || header.isEmpty() ? -1 :
        LocalDateTime.parse(header, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant(ZoneOffset.UTC).toEpochMilli();
    } catch (DateTimeParseException ex) {
      return -1;
    }
  }

  public static String pathOffset(String path, RoutingContext context) {
    final Route route = context.currentRoute();

    // cannot make any assumptions
    if (route == null) {
      return path;
    }

    if (!route.isExactPath()) {
      final String rest = context.pathParam("*");
      if (rest != null) {
        // normalize
        if (rest.length() > 0) {
          if (rest.charAt(0) == '/') {
            return rest;
          } else {
            return "/" + rest;
          }
        } else {
          return "/";
        }
      }
    }
    int prefixLen = 0;
    String mountPoint = context.mountPoint();
    if (mountPoint != null) {
      prefixLen = mountPoint.length();
      // special case we need to verify if a trailing slash  is present and exclude
      if (mountPoint.charAt(mountPoint.length() - 1) == '/') {
        prefixLen--;
      }
    }
    // we can only safely skip the route path if there are no variables or regex
    if (!route.isRegexPath()) {
      String routePath = route.getPath();
      if (routePath != null) {
        prefixLen += routePath.length();
        // special case we need to verify if a trailing slash  is present and exclude
        if (routePath.charAt(routePath.length() - 1) == '/') {
          prefixLen--;
        }
      }
    }
    return prefixLen != 0 ? path.substring(prefixLen) : path;
  }

  public static long secondsFactor(long millis) {
    return millis - (millis % 1000);
  }

  public static boolean isJsonContentType(String contentType) {
    return contentType.contains("application/json") || contentType.contains("+json");
  }

  public static boolean isXMLContentType(String contentType) {
    return contentType.contains("application/xml") || contentType.contains("text/xml") || contentType.contains("+xml");
  }

  public static void addToMapIfAbsent(MultiMap map, CharSequence key, CharSequence value) {
    if (!map.contains(key)) {
      map.set(key, value);
    }
  }

  public static void appendToMapIfAbsent(MultiMap map, CharSequence key, CharSequence sep, CharSequence value) {
    if (!map.contains(key)) {
      map.set(key, value);
    } else {
      String existing = map.get(key);
      map.set(key, existing + sep + value);
    }
  }

  /**
   * RegExp to check for no-cache token in Cache-Control.
   */
  private static final Pattern CACHE_CONTROL_NO_CACHE_REGEXP = Pattern.compile("(?:^|,)\\s*?no-cache\\s*?(?:,|$)");

  public static boolean fresh(RoutingContext ctx) {
    return fresh(ctx, -1);
  }

  public static boolean fresh(RoutingContext ctx, long lastModified) {

    final HttpServerRequest req = ctx.request();
    final HttpServerResponse res = ctx.response();

    // fields
    final String modifiedSince = req.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
    final String noneMatch = req.getHeader(HttpHeaders.IF_NONE_MATCH);

    // unconditional request
    if (modifiedSince == null && noneMatch == null) {
      return false;
    }

    // Always return stale when Cache-Control: no-cache
    // to support end-to-end reload requests
    // https://tools.ietf.org/html/rfc2616#section-14.9.4
    final String cacheControl = req.getHeader(HttpHeaders.CACHE_CONTROL);
    if (cacheControl != null && CACHE_CONTROL_NO_CACHE_REGEXP.matcher(cacheControl).find()) {
      return false;
    }

    // if-none-match
    if (noneMatch != null && !"*".equals(noneMatch)) {
      final String etag = res.headers().get(HttpHeaders.ETAG);

      if (etag == null) {
        return false;
      }

      boolean etagStale = true;

      // lookup etags
      int end = 0;
      int start = 0;

      loop:
      for (int i = 0; i < noneMatch.length(); i++) {
        switch (noneMatch.charAt(i)) {
          case ' ':
            if (start == end) {
              start = end = i + 1;
            }
            break;
          case ',':
            String match = noneMatch.substring(start, end);
            if (match.equals(etag) || match.equals("W/" + etag) || ("W/" + match).equals(etag)) {
              etagStale = false;
              break loop;
            }
            start = end = i + 1;
            break;
          default:
            end = i + 1;
            break;
        }
      }

      if (etagStale) {
        // the parser run out of bytes, need to check if the match is valid
        String match = noneMatch.substring(start, end);
        if (!match.equals(etag) && !match.equals("W/" + etag) && !("W/" + match).equals(etag)) {
          return false;
        }
      }
    }

    // if-modified-since
    if (modifiedSince != null) {
      if (lastModified == -1) {
        // no custom last modified provided, will use the response headers if any
        lastModified = parseRFC1123DateTime(res.headers().get(HttpHeaders.LAST_MODIFIED));
      }

      boolean modifiedStale = lastModified == -1 || !(lastModified <= parseRFC1123DateTime(modifiedSince));

      return !modifiedStale;
    }

    return true;
  }

  public static boolean canUpgradeToWebsocket(HttpServerRequest req) {
    // verify if we can upgrade
    // 1. Connection header contains "Upgrade"
    // 2. Upgrade header is "websocket"
    final MultiMap headers = req.headers();
    if (headers.contains(HttpHeaders.CONNECTION)) {
      for (String connection : headers.getAll(HttpHeaders.CONNECTION)) {
        if (connection.toLowerCase().contains(HttpHeaders.UPGRADE)) {
          if (headers.contains(HttpHeaders.UPGRADE)) {
            for (String upgrade : headers.getAll(HttpHeaders.UPGRADE)) {
              if (upgrade.toLowerCase().contains(HttpHeaders.WEBSOCKET)) {
                return true;
              }
            }
          }
        }
      }
    }

    return false;
  }
}

Output:
【漏洞类型】路径遍历漏洞
【严重性】High
【漏洞摘要】原始代码中的`pathOffset`函数在处理用户提供的路径参数时，未对路径参数进行适当的验证和规范化处理。具体来说，`context.pathParam("*")`直接获取用户输入的路径参数，并在后续处理中使用该参数来生成新的路径。这种情况下，攻击者可以通过构造特殊的路径参数（例如，包含`../`的路径），尝试访问或操作不应被访问的文件系统路径，从而实现路径遍历攻击。
【关键代码模式】
- `context.pathParam("*")`：获取用户输入的路径参数。
- `String rest = context.pathParam("*")`：存储用户输入的路径参数。
- `rest.replace('\\', '/')`：在某些情况下，将反斜杠替换为斜杠，但未进一步处理路径参数。
- `HttpUtils.removeDots(rest.replace('\\', '/'))`：在修复后的代码中，使用`HttpUtils.removeDots`函数来规范化路径参数，防止路径遍历攻击。
【修复方法说明】修复方法通过调用`HttpUtils.removeDots`函数来规范化路径参数，移除任何路径中的`../`或`./`部分，从而确保路径参数不会指向文件系统的其他位置。这解决了路径遍历漏洞问题。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
lib/index.js
### 代码内容：
'use strict';

const internals = {
    operators: ['!', '^', '*', '/', '%', '+', '-', '<', '<=', '>', '>=', '==', '!=', '&&', '||', '??'],
    operatorCharacters: ['!', '^', '*', '/', '%', '+', '-', '<', '=', '>', '&', '|', '?'],
    operatorsOrder: [['^'], ['*', '/', '%'], ['+', '-'], ['<', '<=', '>', '>='], ['==', '!='], ['&&'], ['||', '??']],
    operatorsPrefix: ['!', 'n'],

    literals: {
        '"': '"',
        '`': '`',
        '\'': '\'',
        '[': ']'
    },

    numberRx: /^(?:[0-9]*\.?[0-9]*){1}$/,
    tokenRx: /^[\w\$\#\.\@\:\{\}]+$/,

    symbol: Symbol('formula'),
    settings: Symbol('settings')
};

exports.Parser = class {

    constructor(string, options = {}) {

        if (!options[internals.settings] &&
            options.constants) {

            for (const constant in options.constants) {
                const value = options.constants[constant];
                if (value !== null &&
                    !['boolean', 'number', 'string'].includes(typeof value)) {

                    throw new Error(`Formula constant ${constant} contains invalid ${typeof value} value type`);
                }
            }
        }

        this.settings = options[internals.settings] ? options : Object.assign({ [internals.settings]: true, constants: {}, functions: {} }, options);
        this.single = null;

        this._parts = null;
        this._parse(string);
    }

    _parse(string) {

        let parts = [];
        let current = '';
        let parenthesis = 0;
        let literal = false;

        const flush = (inner) => {

            if (parenthesis) {
                throw new Error('Formula missing closing parenthesis');
            }

            const last = parts.length ? parts[parts.length - 1] : null;

            if (!literal &&
                !current &&
                !inner) {

                return;
            }

            if (last &&
                last.type === 'reference' &&
                inner === ')') {                                                                // Function

                last.type = 'function';
                last.value = this._subFormula(current, last.value);
                current = '';
                return;
            }

            if (inner === ')') {                                                                // Segment
                const sub = new exports.Parser(current, this.settings);
                parts.push({ type: 'segment', value: sub });
            }
            else if (literal) {
                if (literal === ']') {                                                          // Reference
                    parts.push({ type: 'reference', value: current });
                    current = '';
                    return;
                }

                parts.push({ type: 'literal', value: current });                                // Literal
            }
            else if (internals.operatorCharacters.includes(current)) {                          // Operator
                if (last &&
                    last.type === 'operator' &&
                    internals.operators.includes(last.value + current)) {                       // 2 characters operator

                    last.value += current;
                }
                else {
                    parts.push({ type: 'operator', value: current });
                }
            }
            else if (current.match(internals.numberRx)) {                                       // Number
                parts.push({ type: 'constant', value: parseFloat(current) });
            }
            else if (this.settings.constants[current] !== undefined) {                          // Constant
                parts.push({ type: 'constant', value: this.settings.constants[current] });
            }
            else {                                                                              // Reference
                if (!current.match(internals.tokenRx)) {
                    throw new Error(`Formula contains invalid token: ${current}`);
                }

                parts.push({ type: 'reference', value: current });
            }

            current = '';
        };

        for (const c of string) {
            if (literal) {
                if (c === literal) {
                    flush();
                    literal = false;
                }
                else {
                    current += c;
                }
            }
            else if (parenthesis) {
                if (c === '(') {
                    current += c;
                    ++parenthesis;
                }
                else if (c === ')') {
                    --parenthesis;
                    if (!parenthesis) {
                        flush(c);
                    }
                    else {
                        current += c;
                    }
                }
                else {
                    current += c;
                }
            }
            else if (c in internals.literals) {
                literal = internals.literals[c];
            }
            else if (c === '(') {
                flush();
                ++parenthesis;
            }
            else if (internals.operatorCharacters.includes(c)) {
                flush();
                current = c;
                flush();
            }
            else if (c !== ' ') {
                current += c;
            }
            else {
                flush();
            }
        }

        flush();

        // Replace prefix - to internal negative operator

        parts = parts.map((part, i) => {

            if (part.type !== 'operator' ||
                part.value !== '-' ||
                i && parts[i - 1].type !== 'operator') {

                return part;
            }

            return { type: 'operator', value: 'n' };
        });

        // Validate tokens order

        let operator = false;
        for (const part of parts) {
            if (part.type === 'operator') {
                if (internals.operatorsPrefix.includes(part.value)) {
                    continue;
                }

                if (!operator) {
                    throw new Error('Formula contains an operator in invalid position');
                }

                if (!internals.operators.includes(part.value)) {
                    throw new Error(`Formula contains an unknown operator ${part.value}`);
                }
            }
            else if (operator) {
                throw new Error('Formula missing expected operator');
            }

            operator = !operator;
        }

        if (!operator) {
            throw new Error('Formula contains invalid trailing operator');
        }

        // Identify single part

        if (parts.length === 1 &&
            ['reference', 'literal', 'constant'].includes(parts[0].type)) {

            this.single = { type: parts[0].type === 'reference' ? 'reference' : 'value', value: parts[0].value };
        }

        // Process parts

        this._parts = parts.map((part) => {

            // Operators

            if (part.type === 'operator') {
                return internals.operatorsPrefix.includes(part.value) ? part : part.value;
            }

            // Literals, constants, segments

            if (part.type !== 'reference') {
                return part.value;
            }

            // References

            if (this.settings.tokenRx &&
                !this.settings.tokenRx.test(part.value)) {

                throw new Error(`Formula contains invalid reference ${part.value}`);
            }

            if (this.settings.reference) {
                return this.settings.reference(part.value);
            }

            return internals.reference(part.value);
        });
    }

    _subFormula(string, name) {

        const method = this.settings.functions[name];
        if (typeof method !== 'function') {
            throw new Error(`Formula contains unknown function ${name}`);
        }

        let args = [];
        if (string) {
            let current = '';
            let parenthesis = 0;
            let literal = false;

            const flush = () => {

                if (!current) {
                    throw new Error(`Formula contains function ${name} with invalid arguments ${string}`);
                }

                args.push(current);
                current = '';
            };

            for (let i = 0; i < string.length; ++i) {
                const c = string[i];
                if (literal) {
                    current += c;
                    if (c === literal) {
                        literal = false;
                    }
                }
                else if (c in internals.literals &&
                    !parenthesis) {

                    current += c;
                    literal = internals.literals[c];
                }
                else if (c === ',' &&
                    !parenthesis) {

                    flush();
                }
                else {
                    current += c;
                    if (c === '(') {
                        ++parenthesis;
                    }
                    else if (c === ')') {
                        --parenthesis;
                    }
                }
            }

            flush();
        }

        args = args.map((arg) => new exports.Parser(arg, this.settings));

        return function (context) {

            const innerValues = [];
            for (const arg of args) {
                innerValues.push(arg.evaluate(context));
            }

            return method.call(context, ...innerValues);
        };
    }

    evaluate(context) {

        const parts = this._parts.slice();

        // Prefix operators

        for (let i = parts.length - 2; i >= 0; --i) {
            const part = parts[i];
            if (part &&
                part.type === 'operator') {

                const current = parts[i + 1];
                parts.splice(i + 1, 1);
                const value = internals.evaluate(current, context);
                parts[i] = internals.single(part.value, value);
            }
        }

        // Left-right operators

        internals.operatorsOrder.forEach((set) => {

            for (let i = 1; i < parts.length - 1;) {
                if (set.includes(parts[i])) {
                    const operator = parts[i];
                    const left = internals.evaluate(parts[i - 1], context);
                    const right = internals.evaluate(parts[i + 1], context);

                    parts.splice(i, 2);
                    const result = internals.calculate(operator, left, right);
                    parts[i - 1] = result === 0 ? 0 : result;                               // Convert -0
                }
                else {
                    i += 2;
                }
            }
        });

        return internals.evaluate(parts[0], context);
    }
};

exports.Parser.prototype[internals.symbol] = true;

internals.reference = function (name) {

    return function (context) {

        return context && context[name] !== undefined ? context[name] : null;
    };
};

internals.evaluate = function (part, context) {

    if (part === null) {
        return null;
    }

    if (typeof part === 'function') {
        return part(context);
    }

    if (part[internals.symbol]) {
        return part.evaluate(context);
    }

    return part;
};

internals.single = function (operator, value) {

    if (operator === '!') {
        return value ? false : true;
    }

    // operator === 'n'

    const negative = -value;
    if (negative === 0) {       // Override -0
        return 0;
    }

    return negative;
};

internals.calculate = function (operator, left, right) {

    if (operator === '??') {
        return internals.exists(left) ? left : right;
    }

    if (typeof left === 'string' ||
        typeof right === 'string') {

        if (operator === '+') {
            left = internals.exists(left) ? left : '';
            right = internals.exists(right) ? right : '';
            return left + right;
        }
    }
    else {
        switch (operator) {
            case '^': return Math.pow(left, right);
            case '*': return left * right;
            case '/': return left / right;
            case '%': return left % right;
            case '+': return left + right;
            case '-': return left - right;
        }
    }

    switch (operator) {
        case '<': return left < right;
        case '<=': return left <= right;
        case '>': return left > right;
        case '>=': return left >= right;
        case '==': return left === right;
        case '!=': return left !== right;
        case '&&': return left && right;
        case '||': return left || right;
    }

    return null;
};

internals.exists = function (value) {

    return value !== null && value !== undefined;
};

Output:
【漏洞类型】正则表达式漏洞
【严重性】Low
【漏洞摘要】原始代码中，正则表达式 `numberRx` 用于匹配数字，但在特定情况下可能会导致意外匹配。具体来说，`numberRx` 会匹配以点号（`.`）开头的字符串，如 `.5`，这可能不是预期的行为。虽然这种类型的漏洞通常不是高风险的，因为它们可能不会导致严重的安全问题，但它们可能会引入意外的逻辑错误或不一致的结果。
【关键代码模式】
- `numberRx` 正则表达式：`^(?:[0-9]*\.?[0-9]*){1}$`
- `evaluate` 函数：`internals.evaluate(parts[0], context);`
- `calculate` 函数：处理数学运算的部分。
【修复方法说明】修复通过调整 `numberRx` 正则表达式为 `^(?:[0-9]*(\.[0-9]*)?){1}$`，确保数字必须以数字开头，而不是点号，从而避免意外匹配。此修复提高了代码的健壮性和一致性，但并未解决潜在的业务逻辑问题。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
inlong-manager/manager-common/src/main/java/org/apache/inlong/manager/common/consts/InlongConstants.java
### 代码内容：
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.inlong.manager.common.consts;

/**
 * Global constant for the Inlong system.
 */
public class InlongConstants {

    /**
     * Thread pool related config.
     */
    public static final int CORE_POOL_SIZE = 10;
    public static final int MAX_POOL_SIZE = 20;
    public static final long ALIVE_TIME_MS = 100L;
    public static final int QUEUE_SIZE = 10000;

    /**
     * Group config
     */
    public static final String COMMA = ",";

    public static final String SLASH = "/";

    public static final String COLON = ":";

    public static final String SEMICOLON = ";";

    public static final String LEFT_BRACKET = "(";

    public static final String ADMIN_USER = "admin";

    public static final Integer AFFECTED_ONE_ROW = 1;

    public static final Integer INITIAL_VERSION = 1;

    public static final Integer UN_DELETED = 0;

    public static final Integer DELETED_STATUS = 10;

    public static final Integer STANDARD_MODE = 0;
    public static final Integer LIGHTWEIGHT_MODE = 1;

    public static final Integer DISABLE_ZK = 0;
    public static final Integer ENABLE_ZK = 1;

    public static final Integer DISABLE_CREATE_RESOURCE = 0;
    public static final Integer ENABLE_CREATE_RESOURCE = 1;

    /**
     * Data report type, support:
     * <pre>
     *     0: report to DataProxy and respond when the DataProxy received data.
     *     1: report to DataProxy and respond after DataProxy sends data.
     *     2: report to MQ and respond when the MQ received data.
     * </pre>
     */
    public static final Integer REPORT_TO_DP_RECEIVED = 0;
    public static final Integer REPORT_TO_DP_SENT = 1;
    public static final Integer REPORT_TO_MQ_RECEIVED = 2;

    public static final Integer UN_SYNC_SEND = 0;
    public static final Integer SYNC_SEND = 1;

    public static final String BATCH_TASK = "batch.task";

    /**
     * Pulsar config
     */
    public static final String PULSAR_AUTHENTICATION = "pulsar.authentication";

    public static final String PULSAR_AUTHENTICATION_TYPE = "pulsar.authentication.type";

    public static final String DEFAULT_PULSAR_TENANT = "public";

    public static final String DEFAULT_PULSAR_AUTHENTICATION_TYPE = "token";

    public static final String PULSAR_QUEUE_TYPE_SERIAL = "SERIAL";

    public static final String PULSAR_QUEUE_TYPE_PARALLEL = "PARALLEL";

    /**
     * Format of the Pulsar topic: "persistent://tenant/namespace/topic
     */
    public static final String PULSAR_TOPIC_FORMAT = "persistent://%s/%s/%s";

    /**
     * Sort config
     */
    public static final String DATAFLOW = "dataflow";

    public static final String STREAMS = "streams";

    public static final String RELATIONS = "relations";

    public static final String INPUTS = "inputs";

    public static final String OUTPUTS = "outputs";

    public static final String NODES = "nodes";

    public static final String NODE_TYPE = "type";

    public static final String LOAD = "Load";

    public static final String EXTRACT = "Extract";

    public static final String SORT_JOB_ID = "sort.job.id";

    public static final String SORT_TYPE = "sort.type";

    public static final String DEFAULT_SORT_TYPE = "flink";

    public static final String SORT_NAME = "sort.name";

    public static final String SORT_URL = "sort.url";

    public static final String SORT_AUTHENTICATION = "sort.authentication";

    public static final String SORT_AUTHENTICATION_TYPE = "sort.authentication.type";

    public static final String DEFAULT_SORT_AUTHENTICATION_TYPE = "secret_and_token";

    public static final String SORT_PROPERTIES = "sort.properties";

    public static final String DATA_TYPE_RAW_PREFIX = "RAW_";

    public static final int DEFAULT_ENABLE_VALUE = 1;

}

## 内容2
### 文件路径：
inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/sink/mysql/MySQLSinkDTO.java
### 代码内容：
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.inlong.manager.pojo.sink.mysql;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.inlong.manager.common.enums.ErrorCodeEnum;
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.constraints.NotNull;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * MySQL sink info
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MySQLSinkDTO {

    /**
     * The sensitive param may lead the attack.
     */
    private static final Map<String, String> SENSITIVE_PARAM_MAP = new HashMap<String, String>() {

        {
            put("autoDeserialize=true", "autoDeserialize=false");
            put("allowLoadLocalInfile=true", "allowLoadLocalInfile=false");
            put("allowUrlInLocalInfile=true", "allowUrlInLocalInfile=false");
            put("allowLoadLocalInfileInPath=/", "allowLoadLocalInfileInPath=");
        }
    };

    private static final Logger LOGGER = LoggerFactory.getLogger(MySQLSinkDTO.class);

    @ApiModelProperty("MySQL JDBC URL, such as jdbc:mysql://host:port/database")
    private String jdbcUrl;

    @ApiModelProperty("Username for JDBC URL")
    private String username;

    @ApiModelProperty("User password")
    private String password;

    @ApiModelProperty("Target table name")
    private String tableName;

    @ApiModelProperty("Primary key")
    private String primaryKey;

    @ApiModelProperty("Properties for MySQL")
    private Map<String, Object> properties;

    /**
     * Get the dto instance from the request
     *
     * @param request MySQLSinkRequest
     * @return {@link MySQLSinkDTO}
     * @apiNote The config here will be saved to the database, so filter sensitive params before saving.
     */
    public static MySQLSinkDTO getFromRequest(MySQLSinkRequest request) {
        String url = filterSensitive(request.getJdbcUrl());
        return MySQLSinkDTO.builder()
                .jdbcUrl(url)
                .username(request.getUsername())
                .password(request.getPassword())
                .primaryKey(request.getPrimaryKey())
                .tableName(request.getTableName())
                .properties(request.getProperties())
                .build();
    }

    /**
     * Get MySQL sink info from JSON string
     *
     * @param extParams string ext params
     * @return {@link MySQLSinkDTO}
     */
    public static MySQLSinkDTO getFromJson(@NotNull String extParams) {
        try {
            return JsonUtils.parseObject(extParams, MySQLSinkDTO.class);
        } catch (Exception e) {
            throw new BusinessException(ErrorCodeEnum.SINK_INFO_INCORRECT,
                    String.format("parse extParams of MySQL SinkDTO failure: %s", e.getMessage()));
        }
    }

    /**
     * Get MySQL table info
     *
     * @param mySQLSink MySQL sink dto,{@link MySQLSinkDTO}
     * @param columnList MySQL column info list,{@link MySQLColumnInfo}
     * @return {@link MySQLTableInfo}
     */
    public static MySQLTableInfo getTableInfo(MySQLSinkDTO mySQLSink, List<MySQLColumnInfo> columnList) {
        MySQLTableInfo tableInfo = new MySQLTableInfo();
        String dbName = getDbNameFromUrl(mySQLSink.getJdbcUrl());
        tableInfo.setDbName(dbName);
        tableInfo.setTableName(mySQLSink.getTableName());
        tableInfo.setPrimaryKey(mySQLSink.getPrimaryKey());
        tableInfo.setColumns(columnList);
        return tableInfo;
    }

    /**
     * Get DbName from jdbcUrl
     *
     * @param jdbcUrl MySQL JDBC url, such as jdbc:mysql://host:port/database
     * @return database name
     */
    private static String getDbNameFromUrl(String jdbcUrl) {
        String database = null;

        if (Strings.isNullOrEmpty(jdbcUrl)) {
            throw new IllegalArgumentException("Invalid JDBC url.");
        }

        jdbcUrl = jdbcUrl.toLowerCase();
        if (jdbcUrl.startsWith("jdbc:impala")) {
            jdbcUrl = jdbcUrl.replace(":impala", "");
        }

        int pos1;
        if (!jdbcUrl.startsWith("jdbc:")
                || (pos1 = jdbcUrl.indexOf(':', 5)) == -1) {
            throw new IllegalArgumentException("Invalid JDBC url.");
        }

        String connUri = jdbcUrl.substring(pos1 + 1);
        if (connUri.startsWith("//")) {
            int pos = connUri.indexOf('/', 2);
            if (pos != -1) {
                database = connUri.substring(pos + 1);
            }
        } else {
            database = connUri;
        }

        if (Strings.isNullOrEmpty(database)) {
            throw new IllegalArgumentException("Invalid JDBC URL: " + jdbcUrl);
        }

        if (database.contains("?")) {
            database = database.substring(0, database.indexOf("?"));
        }
        if (database.contains(";")) {
            database = database.substring(0, database.indexOf(";"));
        }
        return database;
    }

    /**
     * Filter the sensitive params for the given URL.
     *
     * @param url str may have some sensitive params
     * @return str without sensitive param
     */
    @VisibleForTesting
    protected static String filterSensitive(String url) {
        if (StringUtils.isBlank(url)) {
            return url;
        }
        try {
            String resultUrl = URLDecoder.decode(url, "UTF-8");
            for (String sensitiveParam : SENSITIVE_PARAM_MAP.keySet()) {
                if (StringUtils.containsIgnoreCase(resultUrl, sensitiveParam)) {
                    resultUrl = StringUtils.replaceIgnoreCase(resultUrl, sensitiveParam,
                            SENSITIVE_PARAM_MAP.get(sensitiveParam));
                }
            }
            LOGGER.info("the origin url [{}] was replaced to: [{}]", url, resultUrl);
            return resultUrl;
        } catch (Exception e) {
            throw new BusinessException(ErrorCodeEnum.SINK_INFO_INCORRECT,
                    ErrorCodeEnum.SINK_INFO_INCORRECT.getMessage() + ": " + e.getMessage());
        }
    }

}

Output:
【漏洞类型】路径遍历漏洞
【严重性】High
【漏洞摘要】原始代码中的MySQLSinkDTO类在处理用户输入的JDBC URL时，可能存在路径遍历漏洞。具体表现为，filterSensitive方法仅通过URLDecoder.decode方法解码一次URL，而未验证解码后的URL是否仍然包含敏感参数，如allowLoadLocalInfileInPath=/。这可能导致攻击者通过多次编码嵌入恶意路径，从而绕过过滤。
【关键代码模式】URLDecoder.decode、StringUtils.containsIgnoreCase、StringUtils.replaceIgnoreCase
【修复方法说明】修复方法通过多次解码URL，直到URL中不再包含百分号（%）为止，以确保所有潜在的编码恶意参数都被彻底解码和过滤。