Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java
### 代码内容：
package org.opensearch.security.securityconf;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder.SetMultimapBuilder;
import com.google.common.collect.SetMultimap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Strings;

import org.opensearch.ExceptionsHelper;
import org.opensearch.action.support.IndicesOptions;
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.collect.Tuple;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.transport.TransportAddress;
import org.opensearch.common.util.set.Sets;
import org.opensearch.common.xcontent.NamedXContentRegistry;
import org.opensearch.security.resolver.IndexResolverReplacer.Resolved;
import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7;
import org.opensearch.security.securityconf.impl.v7.RoleMappingsV7;
import org.opensearch.security.securityconf.impl.v7.RoleV7;
import org.opensearch.security.securityconf.impl.v7.RoleV7.Index;
import org.opensearch.security.securityconf.impl.v7.TenantV7;
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.support.WildcardMatcher;
import org.opensearch.security.user.User;

import static org.opensearch.cluster.metadata.IndexAbstraction.Type.ALIAS;

public class ConfigModelV7 extends ConfigModel {

    protected final Logger log = LogManager.getLogger(this.getClass());
    private ConfigConstants.RolesMappingResolution rolesMappingResolution;
    private ActionGroupResolver agr = null;
    private SecurityRoles securityRoles = null;
    private TenantHolder tenantHolder;
    private RoleMappingHolder roleMappingHolder;
    private SecurityDynamicConfiguration<RoleV7> roles;
    private SecurityDynamicConfiguration<TenantV7> tenants;

    public ConfigModelV7(
            SecurityDynamicConfiguration<RoleV7> roles,
            SecurityDynamicConfiguration<RoleMappingsV7> rolemappings,
            SecurityDynamicConfiguration<ActionGroupsV7> actiongroups,
            SecurityDynamicConfiguration<TenantV7> tenants,
            DynamicConfigModel dcm,
            Settings opensearchSettings) {

        this.roles = roles;
        this.tenants = tenants;

        try {
            rolesMappingResolution = ConfigConstants.RolesMappingResolution.valueOf(
                    opensearchSettings.get(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString())
                            .toUpperCase());
        } catch (Exception e) {
            log.error("Cannot apply roles mapping resolution", e);
            rolesMappingResolution = ConfigConstants.RolesMappingResolution.MAPPING_ONLY;
        }

        agr = reloadActionGroups(actiongroups);
        securityRoles = reload(roles);
        tenantHolder = new TenantHolder(roles, tenants);
        roleMappingHolder = new RoleMappingHolder(rolemappings, dcm.getHostsResolverMode());
    }

    public Set<String> getAllConfiguredTenantNames() {
        return Collections.unmodifiableSet(tenants.getCEntries().keySet());
    }

    public SecurityRoles getSecurityRoles() {
        return securityRoles;
    }

    private static interface ActionGroupResolver {
        Set<String> resolvedActions(final List<String> actions);
    }

    private ActionGroupResolver reloadActionGroups(SecurityDynamicConfiguration<ActionGroupsV7> actionGroups) {
        return new ActionGroupResolver() {

            private Set<String> getGroupMembers(final String groupname) {

                if (actionGroups == null) {
                    return Collections.emptySet();
                }

                return Collections.unmodifiableSet(resolve(actionGroups, groupname));
            }

            private Set<String> resolve(final SecurityDynamicConfiguration<?> actionGroups, final String entry) {

                // SG5 format, plain array
                //List<String> en = actionGroups.getAsList(DotPath.of(entry));
                //if (en.isEmpty()) {
                    // try SG6 format including readonly and permissions key
                //  en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS));
                    //}

                if(!actionGroups.getCEntries().containsKey(entry)) {
                    return Collections.emptySet();
                }

                final Set<String> ret = new HashSet<String>();

                final Object actionGroupAsObject = actionGroups.getCEntries().get(entry);

                if(actionGroupAsObject != null && actionGroupAsObject instanceof List) {

                    for (final String perm: ((List<String>) actionGroupAsObject)) {
                        if (actionGroups.getCEntries().keySet().contains(perm)) {
                            ret.addAll(resolve(actionGroups,perm));
                        } else {
                            ret.add(perm);
                        }
                    }

                } else if(actionGroupAsObject != null &&  actionGroupAsObject instanceof ActionGroupsV7) {
                    for (final String perm: ((ActionGroupsV7) actionGroupAsObject).getAllowed_actions()) {
                        if (actionGroups.getCEntries().keySet().contains(perm)) {
                            ret.addAll(resolve(actionGroups,perm));
                        } else {
                            ret.add(perm);
                        }
                    }
                } else {
                    throw new RuntimeException("Unable to handle "+actionGroupAsObject);
                }

                return Collections.unmodifiableSet(ret);
            }

            @Override
            public Set<String> resolvedActions(final List<String> actions) {
                final Set<String> resolvedActions = new HashSet<String>();
                for (String string: actions) {
                    final Set<String> groups = getGroupMembers(string);
                    if (groups.isEmpty()) {
                        resolvedActions.add(string);
                    } else {
                        resolvedActions.addAll(groups);
                    }
                }

                return Collections.unmodifiableSet(resolvedActions);
            }
        };
    }

    private SecurityRoles reload(SecurityDynamicConfiguration<RoleV7> settings) {

        final Set<Future<SecurityRole>> futures = new HashSet<>(5000);
        final ExecutorService execs = Executors.newFixedThreadPool(10);

        for(Entry<String, RoleV7> securityRole: settings.getCEntries().entrySet()) {

            Future<SecurityRole> future = execs.submit(new Callable<SecurityRole>() {

                @Override
                public SecurityRole call() throws Exception {
                    SecurityRole.Builder _securityRole = new SecurityRole.Builder(securityRole.getKey());

                    if(securityRole.getValue() == null) {
                        return null;
                    }

                    final Set<String> permittedClusterActions = agr.resolvedActions(securityRole.getValue().getCluster_permissions());
                    _securityRole.addClusterPerms(permittedClusterActions);

                        /*for(RoleV7.Tenant tenant: securityRole.getValue().getTenant_permissions()) {

                            //if(tenant.equals(user.getName())) {
                            //    continue;
                            //}

                            if(isTenantsRw(tenant)) {
                                _securityRole.addTenant(new Tenant(tenant.getKey(), true));
                            } else {
                                _securityRole.addTenant(new Tenant(tenant.getKey(), false));
                            }
                        }*/

                        for (final Index permittedAliasesIndex : securityRole.getValue().getIndex_permissions()) {

                            final String dls = permittedAliasesIndex.getDls();
                            final List<String> fls = permittedAliasesIndex.getFls();
                            final List<String> maskedFields = permittedAliasesIndex.getMasked_fields();

                            for(String pat: permittedAliasesIndex.getIndex_patterns()) {
                                IndexPattern _indexPattern = new IndexPattern(pat);
                                _indexPattern.setDlsQuery(dls);
                                _indexPattern.addFlsFields(fls);
                                _indexPattern.addMaskedFields(maskedFields);
                                _indexPattern.addPerm(agr.resolvedActions(permittedAliasesIndex.getAllowed_actions()));

                                /*for(Entry<String, List<String>> type: permittedAliasesIndex.getValue().getTypes(-).entrySet()) {
                                    TypePerm typePerm = new TypePerm(type.getKey());
                                    final List<String> perms = type.getValue();
                                    typePerm.addPerms(agr.resolvedActions(perms));
                                    _indexPattern.addTypePerms(typePerm);
                                }*/

                                _securityRole.addIndexPattern(_indexPattern);

                            }

                        }

                        return _securityRole.build();
                }
            });

            futures.add(future);
        }

        execs.shutdown();
        try {
            execs.awaitTermination(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("Thread interrupted (1) while loading roles");
            return null;
        }

        try {
            SecurityRoles _securityRoles = new SecurityRoles(futures.size());
            for (Future<SecurityRole> future : futures) {
                _securityRoles.addSecurityRole(future.get());
            }

            return _securityRoles;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("Thread interrupted (2) while loading roles");
            return null;
        } catch (ExecutionException e) {
            log.error("Error while updating roles: {}", e.getCause(), e.getCause());
            throw ExceptionsHelper.convertToOpenSearchException(e);
        }
    }

    //beans

    public static class SecurityRoles implements org.opensearch.security.securityconf.SecurityRoles {

        protected final Logger log = LogManager.getLogger(this.getClass());

        final Set<SecurityRole> roles;

        private SecurityRoles(int roleCount) {
            roles = new HashSet<>(roleCount);
        }

        private SecurityRoles addSecurityRole(SecurityRole securityRole) {
            if (securityRole != null) {
                this.roles.add(securityRole);
            }
            return this;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((roles == null) ? 0 : roles.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SecurityRoles other = (SecurityRoles) obj;
            if (roles == null) {
                if (other.roles != null)
                    return false;
            } else if (!roles.equals(other.roles))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "roles=" + roles;
        }

        public Set<SecurityRole> getRoles() {
            return Collections.unmodifiableSet(roles);
        }

        public Set<String> getRoleNames() {
            return getRoles().stream().map(r -> r.getName()).collect(Collectors.toSet());
        }

        public SecurityRoles filter(Set<String> keep) {
            final SecurityRoles retVal = new SecurityRoles(roles.size());
            for (SecurityRole sr : roles) {
                if (keep.contains(sr.getName())) {
                    retVal.addSecurityRole(sr);
                }
            }
            return retVal;
        }

        @Override
        public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, IndexNameExpressionResolver resolver, ClusterService cs,
                NamedXContentRegistry namedXContentRegistry) {

            if (!containsDlsFlsConfig()) {
                if(log.isDebugEnabled()) {
                    log.debug("No fls or dls found for {} in {} security roles", user, roles.size());
                }

                return EvaluatedDlsFlsConfig.EMPTY;
            }

            Map<String, Set<String>> dlsQueriesByIndex = new HashMap<String, Set<String>>();
            Map<String, Set<String>> flsFields = new HashMap<String, Set<String>>();
            Map<String, Set<String>> maskedFieldsMap = new HashMap<String, Set<String>>();

            // we capture all concrete indices that do not have any
            // DLS/FLS/Masked Fields restrictions. If the dfm_empty_overwrites_all
            // switch is enabled, this trumps any restrictions on those indices
            // that may be imposed by other roles.
            Set<String> noDlsConcreteIndices = new HashSet<>();
            Set<String> noFlsConcreteIndices = new HashSet<>();
            Set<String> noMaskedFieldConcreteIndices = new HashSet<>();

            for (SecurityRole role : roles) {
                for (IndexPattern ip : role.getIpatterns()) {
					final Set<String> concreteIndices = ip.concreteIndexNames(user, resolver, cs);
					String dls = ip.getDlsQuery(user);

					if (dls != null && dls.length() > 0) {

						for (String concreteIndex : concreteIndices) {
							dlsQueriesByIndex.computeIfAbsent(concreteIndex, (key) -> new HashSet<String>()).add(dls);
						}
					} else if (dfmEmptyOverwritesAll) {
					    noDlsConcreteIndices.addAll(concreteIndices);
					}

                    Set<String> fls = ip.getFls();

                    if (fls != null && fls.size() > 0) {

                        for (String concreteIndex : concreteIndices) {
                            if (flsFields.containsKey(concreteIndex)) {
                                flsFields.get(concreteIndex).addAll(Sets.newHashSet(fls));
                            } else {
                                flsFields.put(concreteIndex, new HashSet<String>());
                                flsFields.get(concreteIndex).addAll(Sets.newHashSet(fls));
                            }
                        }
                    } else if (dfmEmptyOverwritesAll) {
                        noFlsConcreteIndices.addAll(concreteIndices);
                    }

                    Set<String> maskedFields = ip.getMaskedFields();

                    if (maskedFields != null && maskedFields.size() > 0) {

                        for (String concreteIndex : concreteIndices) {
                            if (maskedFieldsMap.containsKey(concreteIndex)) {
                                maskedFieldsMap.get(concreteIndex).addAll(Sets.newHashSet(maskedFields));
                            } else {
                                maskedFieldsMap.put(concreteIndex, new HashSet<String>());
                                maskedFieldsMap.get(concreteIndex).addAll(Sets.newHashSet(maskedFields));
                            }
                        }
                    } else if (dfmEmptyOverwritesAll) {
                        noMaskedFieldConcreteIndices.addAll(concreteIndices);
                    }
                }
            }
            if (dfmEmptyOverwritesAll) {
                if (log.isDebugEnabled()) {
                    log.debug("Index patterns with no dls queries attached: {} - They will be removed from {}", noDlsConcreteIndices,
                            dlsQueriesByIndex.keySet());
                    log.debug("Index patterns with no fls fields attached: {} - They will be removed from {}", noFlsConcreteIndices,
                            flsFields.keySet());
                    log.debug("Index patterns with no masked fields attached: {} - They will be removed from {}", noMaskedFieldConcreteIndices,
                            maskedFieldsMap.keySet());
                }
                // removing the indices that do not have D/M/F restrictions
                // from the keySet will also modify the underlying map
                dlsQueriesByIndex.keySet().removeAll(noDlsConcreteIndices);
                flsFields.keySet().removeAll(noFlsConcreteIndices);
                maskedFieldsMap.keySet().removeAll(noMaskedFieldConcreteIndices);
            }

            return new EvaluatedDlsFlsConfig(dlsQueriesByIndex, flsFields, maskedFieldsMap);
        }

      //opensearchDashboards special only, terms eval
        public Set<String> getAllPermittedIndicesForDashboards(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) {
            Set<String> retVal = new HashSet<>();
            for (SecurityRole sr : roles) {
                retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs));
                retVal.addAll(resolved.getRemoteIndices());
            }
            return Collections.unmodifiableSet(retVal);
        }

        //dnfof only
        public Set<String> reduce(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) {
            Set<String> retVal = new HashSet<>();
            for (SecurityRole sr : roles) {
                retVal.addAll(sr.getAllResolvedPermittedIndices(resolved, user, actions, resolver, cs));
            }
            if (log.isDebugEnabled()) {
                log.debug("Reduced requested resolved indices {} to permitted indices {}.", resolved, retVal.toString());
            }
            return Collections.unmodifiableSet(retVal);
        }

        //return true on success
        public boolean get(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) {
            for (SecurityRole sr : roles) {
                if (ConfigModelV7.impliesTypePerm(sr.getIpatterns(), resolved, user, actions, resolver, cs)) {
                    return true;
                }
            }
            return false;
        }

        public boolean impliesClusterPermissionPermission(String action) {
            return roles.stream().filter(r -> r.impliesClusterPermission(action)).count() > 0;
        }

        //rolespan
        public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver,
                ClusterService cs) {
            Set<IndexPattern> ipatterns = new HashSet<ConfigModelV7.IndexPattern>();
            roles.stream().forEach(p -> ipatterns.addAll(p.getIpatterns()));
            return ConfigModelV7.impliesTypePerm(ipatterns, resolved, user, actions, resolver, cs);
        }

        private boolean containsDlsFlsConfig() {
            for (SecurityRole role : roles) {
                for (IndexPattern ip : role.getIpatterns()) {
                    if (ip.hasDlsQuery() || ip.hasFlsFields() || ip.hasMaskedFields()) {
                        return true;
                    }
                }
            }

            return false;
        }
    }

    public static class SecurityRole {
        private final String name;
        private final Set<IndexPattern> ipatterns;
        private final WildcardMatcher clusterPerms;

        public static final class Builder {
            private final String name;
            private final Set<String> clusterPerms = new HashSet<>();
            private final Set<IndexPattern> ipatterns = new HashSet<>();
            public Builder(String name) {
                this.name = Objects.requireNonNull(name);
            }

            public Builder addIndexPattern(IndexPattern indexPattern) {
                this.ipatterns.add(indexPattern);
                return this;
            }

            public Builder addClusterPerms(Collection<String> clusterPerms) {
                if (clusterPerms != null) {
                    this.clusterPerms.addAll(clusterPerms);
                }
                return this;
            }

            public SecurityRole build() {
                return new SecurityRole(name, ipatterns, WildcardMatcher.from(clusterPerms));
            }
        }

        private SecurityRole(String name, Set<IndexPattern> ipatterns, WildcardMatcher clusterPerms) {
            this.name = Objects.requireNonNull(name);
            this.ipatterns = ipatterns;
            this.clusterPerms = clusterPerms;
        }

        private boolean impliesClusterPermission(String action) {
            return clusterPerms.test(action);
        }

        //get indices which are permitted for the given types and actions
        //dnfof + opensearchDashboards special only
        private Set<String> getAllResolvedPermittedIndices(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver,
                ClusterService cs) {

            final Set<String> retVal = new HashSet<>();
            for (IndexPattern p : ipatterns) {
                //what if we cannot resolve one (for create purposes)
                final boolean patternMatch = p.getPerms().matchAll(actions);

//                final Set<TypePerm> tperms = p.getTypePerms();
//                for (TypePerm tp : tperms) {
//                    if (WildcardMatcher.matchAny(tp.typePattern, resolved.getTypes(-).toArray(new String[0]))) {
//                        patternMatch = WildcardMatcher.matchAll(tp.perms.toArray(new String[0]), actions);
//                    }
//                }
                if (patternMatch) {
                    //resolved but can contain patterns for nonexistent indices
                    final WildcardMatcher permitted = WildcardMatcher.from(p.attemptResolveIndexNames(user, resolver, cs)); //maybe they do not exist
                    final Set<String> res = new HashSet<>();
                    if (!resolved.isLocalAll() && !resolved.getAllIndices().contains("*") && !resolved.getAllIndices().contains("_all")) {
                        //resolved but can contain patterns for nonexistent indices
                        resolved.getAllIndices().stream().filter(permitted).forEach(res::add);
                    } else {
                        //we want all indices so just return what's permitted

                        //#557
                        //final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*");
                        final String[] allIndices = cs.state().metadata().getConcreteAllOpenIndices();
                        Arrays.stream(allIndices).filter(permitted).forEach(res::add);
                    }
                    retVal.addAll(res);
                }
            }

            //all that we want and all thats permitted of them
            return Collections.unmodifiableSet(retVal);
        }

        /*private SecurityRole addTenant(Tenant tenant) {
            if (tenant != null) {
                this.tenants.add(tenant);
            }
            return this;
        }*/

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((clusterPerms == null) ? 0 : clusterPerms.hashCode());
            result = prime * result + ((ipatterns == null) ? 0 : ipatterns.hashCode());
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            //result = prime * result + ((tenants == null) ? 0 : tenants.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SecurityRole other = (SecurityRole) obj;
            if (clusterPerms == null) {
                if (other.clusterPerms != null)
                    return false;
            } else if (!clusterPerms.equals(other.clusterPerms))
                return false;
            if (ipatterns == null) {
                if (other.ipatterns != null)
                    return false;
            } else if (!ipatterns.equals(other.ipatterns))
                return false;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
//            if (tenants == null) {
//                if (other.tenants != null)
//                    return false;
//            } else if (!tenants.equals(other.tenants))
//                return false;
            return true;
        }

        @Override
        public String toString() {
            return System.lineSeparator() + "  " + name + System.lineSeparator()
                    + "    ipatterns=" + ipatterns + System.lineSeparator() + "    clusterPerms=" + clusterPerms;
        }

        //public Set<Tenant> getTenants(User user) {
        //    //TODO filter out user tenants
        //    return Collections.unmodifiableSet(tenants);
        //}

        public Set<IndexPattern> getIpatterns() {
            return Collections.unmodifiableSet(ipatterns);
        }

        public String getName() {
            return name;
        }

    }

    //sg roles
    public static class IndexPattern {
        private final String indexPattern;
        private String dlsQuery;
        private final Set<String> fls = new HashSet<>();
        private final Set<String> maskedFields = new HashSet<>();
        private final Set<String> perms = new HashSet<>();

        public IndexPattern(String indexPattern) {
            super();
            this.indexPattern = Objects.requireNonNull(indexPattern);
        }

        public IndexPattern addFlsFields(List<String> flsFields) {
            if (flsFields != null) {
                this.fls.addAll(flsFields);
            }
            return this;
        }

        public IndexPattern addMaskedFields(List<String> maskedFields) {
            if (maskedFields != null) {
                this.maskedFields.addAll(maskedFields);
            }
            return this;
        }

        public IndexPattern addPerm(Set<String> perms) {
            if (perms != null) {
                this.perms.addAll(perms);
            }
            return this;
        }

        public IndexPattern setDlsQuery(String dlsQuery) {
            if (dlsQuery != null) {
                this.dlsQuery = dlsQuery;
            }
            return this;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((dlsQuery == null) ? 0 : dlsQuery.hashCode());
            result = prime * result + ((fls == null) ? 0 : fls.hashCode());
            result = prime * result + ((maskedFields == null) ? 0 : maskedFields.hashCode());
            result = prime * result + ((indexPattern == null) ? 0 : indexPattern.hashCode());
            result = prime * result + ((perms == null) ? 0 : perms.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            IndexPattern other = (IndexPattern) obj;
            if (dlsQuery == null) {
                if (other.dlsQuery != null)
                    return false;
            } else if (!dlsQuery.equals(other.dlsQuery))
                return false;
            if (fls == null) {
                if (other.fls != null)
                    return false;
            } else if (!fls.equals(other.fls))
                return false;
            if (maskedFields == null) {
                if (other.maskedFields != null)
                    return false;
            } else if (!maskedFields.equals(other.maskedFields))
                return false;
            if (indexPattern == null) {
                if (other.indexPattern != null)
                    return false;
            } else if (!indexPattern.equals(other.indexPattern))
                return false;
            if (perms == null) {
                if (other.perms != null)
                    return false;
            } else if (!perms.equals(other.perms))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return System.lineSeparator() + "        indexPattern=" + indexPattern + System.lineSeparator() + "          dlsQuery=" + dlsQuery
                    + System.lineSeparator() + "          fls=" + fls + System.lineSeparator() + "          perms=" + perms;
        }

        public String getUnresolvedIndexPattern(User user) {
            return replaceProperties(indexPattern, user);
        }

        /** Finds the indices accessible to the user and resolves them to concrete names */
        public Set<String> concreteIndexNames(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs) {
            return getResolvedIndexPattern(user, resolver, cs, false);
        }

        /** Finds the indices accessible to the user and attempts to resolve them to names, also includes any unresolved names */
        public Set<String> attemptResolveIndexNames(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs) {
            return getResolvedIndexPattern(user, resolver, cs, true);
        }

        public Set<String> getResolvedIndexPattern(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs, final boolean appendUnresolved) {
            final String unresolved = getUnresolvedIndexPattern(user);
            final ImmutableSet.Builder<String> resolvedIndices = new ImmutableSet.Builder<>();

            final WildcardMatcher matcher = WildcardMatcher.from(unresolved);
            if (!(matcher instanceof WildcardMatcher.Exact)) {
                final String[] aliasesForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream()
                        .filter(e -> e.getValue().getType() == ALIAS)
                        .filter(e -> matcher.test(e.getKey()))
                        .map(e -> e.getKey())
                        .toArray(String[]::new);
                if (aliasesForPermittedPattern.length > 0) {
                    final String[] resolvedAliases = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern);
                    resolvedIndices.addAll(Arrays.asList(resolvedAliases));
                }
            }

            if (Strings.isNotBlank(unresolved)) {
                final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), unresolved);
                resolvedIndices.addAll(Arrays.asList(resolvedIndicesFromPattern));
            }

            if (appendUnresolved || resolvedIndices.build().isEmpty()) {
                resolvedIndices.add(unresolved);
            }
            return resolvedIndices.build();
        }

        public String getDlsQuery(User user) {
            return replaceProperties(dlsQuery, user);
        }

        public boolean hasDlsQuery() {
            return dlsQuery != null && !dlsQuery.isEmpty();
        }

        public Set<String> getFls() {
            return Collections.unmodifiableSet(fls);
        }

        public boolean hasFlsFields() {
            return fls != null && !fls.isEmpty();
        }

        public Set<String> getMaskedFields() {
            return Collections.unmodifiableSet(maskedFields);
        }

        public boolean hasMaskedFields() {
            return maskedFields != null && !maskedFields.isEmpty();
        }

        public WildcardMatcher getPerms() {
            return WildcardMatcher.from(perms);
        }

    }

    /*public static class TypePerm {
        private final String typePattern;
        private final Set<String> perms = new HashSet<>();

        private TypePerm(String typePattern) {
            super();
            this.typePattern = Objects.requireNonNull(typePattern);
            /*if(IGNORED_TYPES.contains(typePattern)) {
                throw new RuntimeException("typepattern '"+typePattern+"' not allowed");
            }
        }

        private TypePerm addPerms(Collection<String> perms) {
            if (perms != null) {
                this.perms.addAll(perms);
            }
            return this;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((perms == null) ? 0 : perms.hashCode());
            result = prime * result + ((typePattern == null) ? 0 : typePattern.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            TypePerm other = (TypePerm) obj;
            if (perms == null) {
                if (other.perms != null)
                    return false;
            } else if (!perms.equals(other.perms))
                return false;
            if (typePattern == null) {
                if (other.typePattern != null)
                    return false;
            } else if (!typePattern.equals(other.typePattern))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return System.lineSeparator() + "             typePattern=" + typePattern + System.lineSeparator() + "             perms=" + perms;
        }

        public String getTypePattern() {
            return typePattern;
        }

        public Set<String> getPerms() {
            return Collections.unmodifiableSet(perms);
        }

    }*/

    public static class Tenant {
        private final String tenant;
        private final boolean readWrite;

        private Tenant(String tenant, boolean readWrite) {
            super();
            this.tenant = tenant;
            this.readWrite = readWrite;
        }

        public String getTenant() {
            return tenant;
        }

        public boolean isReadWrite() {
            return readWrite;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + (readWrite ? 1231 : 1237);
            result = prime * result + ((tenant == null) ? 0 : tenant.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Tenant other = (Tenant) obj;
            if (readWrite != other.readWrite)
                return false;
            if (tenant == null) {
                if (other.tenant != null)
                    return false;
            } else if (!tenant.equals(other.tenant))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return System.lineSeparator() + "                tenant=" + tenant + System.lineSeparator() + "                readWrite=" + readWrite;
        }
    }

    private static String replaceProperties(String orig, User user) {

        if (user == null || orig == null) {
            return orig;
        }

        orig = orig.replace("${user.name}", user.getName()).replace("${user_name}", user.getName());
        orig = replaceRoles(orig, user);
        orig = replaceSecurityRoles(orig, user);
        for (Entry<String, String> entry : user.getCustomAttributesMap().entrySet()) {
            if (entry == null || entry.getKey() == null || entry.getValue() == null) {
                continue;
            }
            orig = orig.replace("${" + entry.getKey() + "}", entry.getValue());
            orig = orig.replace("${" + entry.getKey().replace('.', '_') + "}", entry.getValue());
        }
        return orig;
    }

    private static String replaceRoles(final String orig, final User user) {
        String retVal = orig;
        if (orig.contains("${user.roles}") || orig.contains("${user_roles}")) {
            final String commaSeparatedRoles = toQuotedCommaSeparatedString(user.getRoles());
            retVal = orig.replace("${user.roles}", commaSeparatedRoles).replace("${user_roles}", commaSeparatedRoles);
        }
        return retVal;
    }

    private static String replaceSecurityRoles(final String orig, final User user) {
        String retVal = orig;
        if (orig.contains("${user.securityRoles}") || orig.contains("${user_securityRoles}")) {
            final String commaSeparatedRoles = toQuotedCommaSeparatedString(user.getSecurityRoles());
            retVal = orig.replace("${user.securityRoles}", commaSeparatedRoles).replace("${user_securityRoles}", commaSeparatedRoles);
        }
        return retVal;
    }

    private static String toQuotedCommaSeparatedString(final Set<String> roles) {
        return Joiner.on(',').join(Iterables.transform(roles, s -> {
            return new StringBuilder(s.length() + 2).append('"').append(s).append('"').toString();
        }));
    }

    private static final class IndexMatcherAndPermissions {
        private WildcardMatcher matcher;
        private WildcardMatcher perms;
        public IndexMatcherAndPermissions(Set<String> patterns, Set<String> perms) {
            this.matcher = WildcardMatcher.from(patterns);
            this.perms = WildcardMatcher.from(perms);
        }

        public boolean matches(String index, String action) {
            return matcher.test(index) && perms.test(action);
        }
    }

    private static boolean impliesTypePerm(Set<IndexPattern> ipatterns, Resolved resolved, User user, String[] requestedActions,
                                           IndexNameExpressionResolver resolver, ClusterService cs) {
        Set<String> resolvedRequestedIndices = resolved.getAllIndices();
        IndexMatcherAndPermissions[] indexMatcherAndPermissions;
        if (resolved.isLocalAll()) {
            indexMatcherAndPermissions = ipatterns
                    .stream()
                    .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user)))
                    .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms))
                    .toArray(IndexMatcherAndPermissions[]::new);
        } else {
            indexMatcherAndPermissions = ipatterns
                    .stream()
                    .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms))
                    .toArray(IndexMatcherAndPermissions[]::new);
        }
        return resolvedRequestedIndices
                .stream()
                .allMatch(index ->
                        Arrays.stream(requestedActions).allMatch(action ->
                                Arrays.stream(indexMatcherAndPermissions).anyMatch(ipap ->
                                        ipap.matches(index, action)
                                )
                        )
                );
    }

    private class TenantHolder {

        private SetMultimap<String, Tuple<String, Boolean>> tenantsMM = null;

        public TenantHolder(SecurityDynamicConfiguration<RoleV7> roles, SecurityDynamicConfiguration<TenantV7> definedTenants) {
            final Set<Future<Tuple<String, Set<Tuple<String, Boolean>>>>> futures = new HashSet<>(roles.getCEntries().size());

            final ExecutorService execs = Executors.newFixedThreadPool(10);

            for(Entry<String, RoleV7> securityRole: roles.getCEntries().entrySet()) {

                if(securityRole.getValue() == null) {
                    continue;
                }

                Future<Tuple<String, Set<Tuple<String, Boolean>>>> future = execs.submit(new Callable<Tuple<String, Set<Tuple<String, Boolean>>>>() {
                    @Override
                    public Tuple<String, Set<Tuple<String, Boolean>>> call() throws Exception {
                        final Set<Tuple<String, Boolean>> tuples = new HashSet<>();
                        final List<RoleV7.Tenant> tenants = securityRole.getValue().getTenant_permissions();
                        if (tenants != null) {

                            for (RoleV7.Tenant tenant : tenants) {

                                // find Wildcarded tenant patterns
                                List<String> matchingTenants = WildcardMatcher.from(tenant.getTenant_patterns()).getMatchAny(definedTenants.getCEntries().keySet(), Collectors.toList()) ;
                                for(String matchingTenant: matchingTenants ) {
                                    tuples.add(new Tuple<String, Boolean>(matchingTenant, agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write")));
                                }
                                // find parameter substitution specified tenant
                                Pattern parameterPattern = Pattern.compile("^\\$\\{attr");
                                List<String> matchingParameterTenantList = tenant.getTenant_patterns().stream().filter(parameterPattern.asPredicate()).collect(Collectors.toList());
                                for(String matchingParameterTenant : matchingParameterTenantList ) {
                                    tuples.add(new Tuple<String, Boolean>(matchingParameterTenant,agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write"))) ;
                                }
                            }
                        }

                        return new Tuple<String, Set<Tuple<String, Boolean>>>(securityRole.getKey(), tuples);
                    }
                });

                futures.add(future);

            }

            execs.shutdown();
            try {
                execs.awaitTermination(30, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("Thread interrupted (1) while loading roles");
                return;
            }

            try {
                final SetMultimap<String, Tuple<String, Boolean>> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()).hashSetValues(16).build();

                for (Future<Tuple<String, Set<Tuple<String, Boolean>>>> future : futures) {
                    Tuple<String, Set<Tuple<String, Boolean>>> result = future.get();
                    tenantsMM_.putAll(result.v1(), result.v2());
                }

                tenantsMM = tenantsMM_;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("Thread interrupted (2) while loading roles");
                return;
            } catch (ExecutionException e) {
                log.error("Error while updating roles: {}", e.getCause(), e.getCause());
                throw ExceptionsHelper.convertToOpenSearchException(e);
            }

        }

        public Map<String, Boolean> mapTenants(final User user, Set<String> roles) {

            if (user == null || tenantsMM == null) {
                return Collections.emptyMap();
            }

            final Map<String, Boolean> result = new HashMap<>(roles.size());
            result.put(user.getName(), true);

            tenantsMM.entries().stream().filter(e -> roles.contains(e.getKey())).filter(e -> !user.getName().equals(e.getValue().v1())).forEach(e -> {

                // replaceProperties for tenant name because
                // at this point e.getValue().v1() can be in this form : "${attr.[internal|jwt|proxy|ldap].*}"
                // let's substitute it with the eventual value of the user's attribute
                final String tenant = replaceProperties(e.getValue().v1(),user);
                final boolean rw = e.getValue().v2();

                if (rw || !result.containsKey(tenant)) { //RW outperforms RO

                    // We want to make sure that we add a tenant that exists
                    // Indeed, because we don't have control over what will be
                    // passed on as values of users' attributes, we have to make
                    // sure that we don't allow them to select tenants that do not exist.
                    if(ConfigModelV7.this.tenants.getCEntries().containsKey(tenant)) {
                        result.put(tenant, rw);
                    }
                }
            });

            Set<String> _roles = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
            _roles.addAll(roles);
            if(!result.containsKey("global_tenant") && (
                    _roles.contains("kibana_user")
                    || _roles.contains("all_access")
                    )) {
                result.put("global_tenant", true);
            }

            return Collections.unmodifiableMap(result);
        }
    }

    private class RoleMappingHolder {

        private ListMultimap<String, String> users;
        private ListMultimap<List<WildcardMatcher>, String> abars;
        private ListMultimap<String, String> bars;
        private ListMultimap<String, String> hosts;
        private final String hostResolverMode;

        private List<WildcardMatcher> userMatchers;
        private List<WildcardMatcher> barMatchers;
        private List<WildcardMatcher> hostMatchers;

        private RoleMappingHolder(final SecurityDynamicConfiguration<RoleMappingsV7> rolemappings, final String hostResolverMode) {

            this.hostResolverMode = hostResolverMode;

            if (roles != null) {

                users = ArrayListMultimap.create();
                abars = ArrayListMultimap.create();
                bars = ArrayListMultimap.create();
                hosts = ArrayListMultimap.create();

                for (final Entry<String, RoleMappingsV7> roleMap : rolemappings.getCEntries().entrySet()) {
                    final String roleMapKey = roleMap.getKey();
                    final RoleMappingsV7 roleMapValue = roleMap.getValue();

                    for (String u : roleMapValue.getUsers()) {
                        users.put(u, roleMapKey);
                    }

                    final Set<String> abar = new HashSet<>(roleMapValue.getAnd_backend_roles());

                    if (!abar.isEmpty()) {
                        abars.put(WildcardMatcher.matchers(abar), roleMapKey);
                    }

                    for (String bar : roleMapValue.getBackend_roles()) {
                        bars.put(bar, roleMapKey);
                    }

                    for (String host : roleMapValue.getHosts()) {
                        hosts.put(host, roleMapKey);
                    }
                }

                userMatchers = WildcardMatcher.matchers(users.keySet());
                barMatchers = WildcardMatcher.matchers(bars.keySet());
                hostMatchers = WildcardMatcher.matchers(hosts.keySet());
            }
        }

        private Set<String> map(final User user, final TransportAddress caller) {

            if (user == null || users == null || abars == null || bars == null || hosts == null) {
                return Collections.emptySet();
            }

            final Set<String> securityRoles = new HashSet<>(user.getSecurityRoles());

            if (rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH
                    || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) {
                if (log.isDebugEnabled()) {
                    log.debug("Pass backendroles from {}", user);
                }
                securityRoles.addAll(user.getRoles());
            }

            if (((rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH
                    || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) {

                for (String p : WildcardMatcher.getAllMatchingPatterns(userMatchers, user.getName())) {
                    securityRoles.addAll(users.get(p));
                }
                for (String p : WildcardMatcher.getAllMatchingPatterns(barMatchers, user.getRoles())) {
                    securityRoles.addAll(bars.get(p));
                }

                for (List<WildcardMatcher> patterns : abars.keySet()) {
                    if (patterns.stream().allMatch(p -> p.matchAny(user.getRoles()))) {
                        securityRoles.addAll(abars.get(patterns));
                    }
                }

                if (caller != null) {
                    //IPV4 or IPv6 (compressed and without scope identifiers)
                    final String ipAddress = caller.getAddress();

                    for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, ipAddress)) {
                        securityRoles.addAll(hosts.get(p));
                    }

                    if (caller.address() != null
                            && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) {
                        final String hostName = caller.address().getHostString();

                        for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, hostName)) {
                            securityRoles.addAll(hosts.get(p));
                        }
                    }

                    if (caller.address() != null && hostResolverMode.equalsIgnoreCase("ip-hostname-lookup")) {

                        final String resolvedHostName = caller.address().getHostName();

                        for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, resolvedHostName)) {
                            securityRoles.addAll(hosts.get(p));
                        }
                    }
                }
            }

            return Collections.unmodifiableSet(securityRoles);

        }
    }

    public Map<String, Boolean> mapTenants(User user, Set<String> roles) {
        return tenantHolder.mapTenants(user, roles);
    }

    public Set<String> mapSecurityRoles(User user, TransportAddress caller) {
        return roleMappingHolder.map(user, caller);
    }
}

Output:
【漏洞类型】数据流未处理的路径解析漏洞
【严重性】Medium
【漏洞摘要】在原始代码中，`IndexPattern` 类的 `concreteIndexNames` 和 `attemptResolveIndexNames` 方法在解析通配符索引模式时，仅处理了别名（ALIAS），而忽略了数据流（DATA_STREAM）。这可能导致在数据流相关的通配符模式下无法正确解析和获取数据流的实际索引名称，从而影响安全策略的执行。
【关键代码模式】
- `IndexPattern` 类的 `concreteIndexNames` 和 `attemptResolveIndexNames` 方法
- `WildcardMatcher` 类的 `from` 和 `getAllMatchingPatterns` 方法
- `ClusterService` 类的 `state().getMetadata().getIndicesLookup().entrySet().stream()` 方法
- `IndicesOptions.lenientExpandOpen()` 方法
【修复方法说明】修复方法通过在 `IndexPattern` 类的 `concreteIndexNames` 和 `attemptResolveIndexNames` 方法中增加对数据流（DATA_STREAM）的处理，确保在解析通配符索引模式时能够正确获取数据流的实际索引名称，从而保障安全策略的准确执行。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/com/openkm/util/FileUtils.java
### 代码内容：
package com.openkm.util;

import com.openkm.core.Config;
import com.openkm.core.DatabaseException;
import com.openkm.dao.MimeTypeDAO;
import com.openkm.dao.bean.MimeType;
import com.openkm.util.impexp.RepositoryImporter;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;

public class FileUtils {
	private static Logger log = LoggerFactory.getLogger(FileUtils.class);

	/**
	 * Returns the name of the file without the extension.
	 */
	public static String getFileName(String file) {
		log.debug("getFileName({})", file);
		int idx = file.lastIndexOf(".");
		String ret = idx >= 0 ? file.substring(0, idx) : file;
		log.debug("getFileName: {}", ret);
		return ret;
	}

	/**
	 * Returns the filename extension.
	 */
	public static String getFileExtension(String file) {
		log.debug("getFileExtension({})", file);
		int idx = file.lastIndexOf(".");
		String ret = idx >= 0 ? file.substring(idx + 1) : "";
		log.debug("getFileExtension: {}", ret);
		return ret;
	}

	/**
	 * Creates a temporal and unique directory
	 *
	 * @throws IOException If something fails.
	 */
	public static File createTempDir() throws IOException {
		File tmpFile = File.createTempFile("okm", null);

		if (!tmpFile.delete())
			throw new IOException();
		if (!tmpFile.mkdir())
			throw new IOException();
		return tmpFile;
	}

	/**
	 * Create temp file
	 */
	public static File createTempFile() throws IOException {
		return File.createTempFile("okm", ".tmp");
	}

	/**
	 * Create temp file
	 */
	public static File createTempFile(String ext) throws IOException {
		return File.createTempFile("okm", "." + ext);
	}

	/**
	 * Create temp file with extension from mime
	 */
	public static File createTempFileFromMime(String mimeType) throws DatabaseException, IOException {
		MimeType mt = MimeTypeDAO.findByName(mimeType);
		String ext = mt.getExtensions().iterator().next();
		return File.createTempFile("okm", "." + ext);
	}

	/**
	 * Wrapper for FileUtils.deleteQuietly
	 *
	 * @param file File or directory to be deleted.
	 */
	public static boolean deleteQuietly(File file) {
		return org.apache.commons.io.FileUtils.deleteQuietly(file);
	}

	/**
	 * Wrapper for FileUtils.cleanDirectory
	 *
	 * @param file File or directory to be deleted.
	 */
	public static void cleanDirectory(File directory) throws IOException {
		org.apache.commons.io.FileUtils.cleanDirectory(directory);
	}

	/**
	 * Wrapper for FileUtils.listFiles
	 *
	 * @param file File or directory to be listed.
	 */
	@SuppressWarnings("unchecked")
	public static Collection<File> listFiles(File directory, String[] extensions, boolean recursive) {
		return org.apache.commons.io.FileUtils.listFiles(directory, extensions, recursive);
	}

	/**
	 * Wrapper for FileUtils.readFileToByteArray
	 *
	 * @param file File or directory to be deleted.
	 */
	public static byte[] readFileToByteArray(File file) throws IOException {
		return org.apache.commons.io.FileUtils.readFileToByteArray(file);
	}

	/**
	 * Delete directory if empty
	 */
	public static void deleteEmpty(File file) {
		if (file.isDirectory()) {
			if (file.list().length == 0) {
				file.delete();
			}
		}
	}

	/**
	 * Count files and directories from a selected directory.
	 */
	public static int countFiles(File dir) {
		File[] found = dir.listFiles();
		int ret = 0;

		if (found != null) {
			for (int i = 0; i < found.length; i++) {
				if (found[i].isDirectory()) {
					ret += countFiles(found[i]);
				}

				ret++;
			}
		}

		return ret;
	}

	/**
	 * Count files and directories from a selected directory.
	 * This version exclude .okm files
	 */
	public static int countImportFiles(File dir) {
		File[] found = dir.listFiles(new RepositoryImporter.NoVersionFilenameFilter());
		int ret = 0;

		if (found != null) {
			for (int i = 0; i < found.length; i++) {
				//log.info("File: {}", found[i].getPath());

				if (found[i].isDirectory()) {
					ret += countImportFiles(found[i]);
				}

				// NAND
				if (!(found[i].isFile() && found[i].getName().toLowerCase().endsWith(Config.EXPORT_METADATA_EXT))) {
					ret++;
				}
			}
		}

		return ret;
	}

	/**
	 * Copy InputStream to File.
	 */
	public static void copy(InputStream input, File output) throws IOException {
		FileOutputStream fos = new FileOutputStream(output);
		IOUtils.copy(input, fos);
		fos.flush();
		fos.close();
	}

	/**
	 * Copy Reader to File.
	 */
	public static void copy(Reader input, File output) throws IOException {
		FileOutputStream fos = new FileOutputStream(output);
		IOUtils.copy(input, fos);
		fos.flush();
		fos.close();
	}

	/**
	 * Copy File to OutputStream
	 */
	public static void copy(File input, OutputStream output) throws IOException {
		FileInputStream fis = new FileInputStream(input);
		IOUtils.copy(fis, output);
		fis.close();
	}

	/**
	 * Copy File to File
	 */
	public static void copy(File input, File output) throws IOException {
		org.apache.commons.io.FileUtils.copyFile(input, output);
	}

	/**
	 * Create "year / month / day" directory structure.
	 */
	public static File createDateDir(String parent) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy" + File.separator + "MM" + File.separator + "dd");
		File dateDir = new File(parent, sdf.format(new Date()));

		if (!dateDir.exists()) {
			dateDir.mkdirs();
		}

		return dateDir;
	}

	/**
	 * Remove reserved characters from filename
	 *
	 * https://msdn.microsoft.com/en-us/library/aa365247
	 */
	public static String toValidFilename(String filename) {
		return filename.replaceAll("[\\\\/:\"*?<>|]+", "");
	}
}

Output:
【漏洞类型】临时目录劫持或信息泄露漏洞
【严重性】高
【漏洞摘要】原始代码中存在创建临时目录的漏洞，具体表现为`createTempDir`方法中使用`File.createTempFile`创建一个临时文件后立即删除，然后尝试创建一个同名的目录。由于`File.createTempFile`的文件名生成是基于系统随机数，恶意用户可能通过竞争条件（race condition）攻击创建同名文件，从而导致临时目录劫持或信息泄露。问题行在`createTempDir`方法中，涉及删除和重新创建文件的逻辑。
【关键代码模式】`File.createTempFile`，`File.delete`，`File.mkdir`，未验证的文件操作。
【修复方法说明】修复方法通过使用`Files.createTempDirectory`替代`File.createTempFile`和`File.mkdir`的组合，解决了竞争条件问题，确保了临时目录的安全创建。最佳实践是使用现代Java文件API以避免此类低级文件操作错误。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
microservices/launcher/src/main/java/com/manydesigns/portofino/microservices/launcher/WarFileLauncher.java
### 代码内容：
package com.manydesigns.portofino.microservices.launcher;

import org.apache.commons.io.IOUtils;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class WarFileLauncher {

    public static void main (String[] args) throws URISyntaxException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        URI uri = WarFileLauncher.class.getProtectionDomain().getCodeSource().getLocation().toURI();
        String pathFromUri;
        if(uri.getScheme().equals("jar")) {
            pathFromUri = new URI(uri.getSchemeSpecificPart()).getSchemeSpecificPart();
            int index = pathFromUri.indexOf("!");
            if(index >= 0) {
                pathFromUri = pathFromUri.substring(0, index);
            }
        } else {
            pathFromUri = uri.toString();
        }
        File warFile = new File(pathFromUri);
        String warPath = warFile.getAbsolutePath();
        if(!warFile.exists()) {
            System.err.println("Could not determine the path of the running war file: " + warPath);
            System.exit(1);
        }
        int port = 8080;
        File tempDir = createTempDir(port);
        try(JarFile jarFile = new JarFile(warFile)) {
            Enumeration<JarEntry> entries = jarFile.entries();
            List<URL> tomcatLibs = new ArrayList<>();
            File tomcatLibDir = new File(tempDir, "tomcat-libs");
            tomcatLibDir.mkdir();
            while(entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if(entry.getName().startsWith("WEB-INF/lib-provided") && entry.getName().endsWith(".jar")) {
                    File outFile = new File(tomcatLibDir, entry.getName().substring("WEB-INF/lib-provided/".length()));
                    if (!outFile.toPath().normalize().startsWith(tomcatLibDir.toPath())) {
                        throw new IOException("Bad zip entry: " + entry.getName());
                    }
                    try(InputStream in = jarFile.getInputStream(entry);
                        OutputStream out = new FileOutputStream(outFile)) {
                        IOUtils.copy(in, out);
                    }
                    tomcatLibs.add(outFile.toURI().toURL());
                }
            }
            File pkgFile = tomcatLibDir;
            String packageName = TomcatLauncher.class.getPackage().getName();
            for(String pkg : packageName.split("\\.")) {
                pkgFile = new File(pkgFile, pkg);
            }
            pkgFile.mkdirs();
            File outFile = new File(pkgFile, "TomcatLauncher.class");
            try(InputStream in = TomcatLauncher.class.getResourceAsStream("TomcatLauncher.class");
                OutputStream out = new FileOutputStream(outFile)) {
                IOUtils.copy(in, out);
            }
            tomcatLibs.add(tomcatLibDir.toURI().toURL());
            URLClassLoader tomcatClassLoader = new URLClassLoader(tomcatLibs.toArray(new URL[0]));

            Class<?> launcherClass = tomcatClassLoader.loadClass(TomcatLauncher.class.getName());
            Method launcherMethod = launcherClass.getMethod("launch", File.class, String.class, Integer.TYPE);
            launcherMethod.invoke(null, tempDir, warPath, port);
        }
    }

    /**
     * Returns the absolute path to the temp directory.
     */
    public static File createTempDir(int port) throws IOException {
        File tempDir = File.createTempFile("portofino.tomcat.", "." + port);
        tempDir.delete();
        tempDir.mkdir();
        tempDir.deleteOnExit();
        return tempDir;
    }

}

Output:
【漏洞类型】创建具有不安全权限的临时文件
【严重性】High
【漏洞摘要】此代码存在路径遍历漏洞，具体表现为在创建临时目录时，未验证目录路径的安全性，可能导致临时目录被恶意用户或程序劫持，进而引发信息泄露或其他安全问题。问题行在原始代码的80-83行，创建临时目录的方式存在安全风险。
【关键代码模式】`File.createTempFile("portofino.tomcat.", "." + port)`、`tempDir.delete()`、`tempDir.mkdir()`、`FileOutputStream`、`deleteOnExit()`
【修复方法说明】使用`Files.createTempDirectory`创建临时目录，确保创建的目录具有安全的权限，从而解决了路径遍历漏洞问题。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
appserver/web/web-core/src/main/java/org/apache/catalina/core/StandardContextValve.java
### 代码内容：
// Portions Copyright [2016-2022] [Payara Foundation and/or its affiliates]

package org.apache.catalina.core;

import org.apache.catalina.*;
import org.apache.catalina.valves.ValveBase;
import org.glassfish.web.valve.GlassFishValve;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;
import java.util.regex.Pattern;

import org.glassfish.grizzly.utils.Charsets;

/**
 * Valve that implements the default basic behavior for the
 * StandardContext container implementation.
 *

 * USAGE CONSTRAINT:  This implementation is likely to be useful only
 * when processing HTTP requests.
 *
 * @author Craig R. McClanahan
 * @version $Revision: 1.19 $ $Date: 2007/05/05 05:31:54 $
 */
final class StandardContextValve
    extends ValveBase {

    // ----------------------------------------------------- Instance Variables

    /**
     * The descriptive information related to this implementation.
     */
    private static final String info =
        "org.apache.catalina.core.StandardContextValve/1.0";

    private StandardContext context = null;

    private static final Pattern PATTERN_META_INF = Pattern.compile("[.]{2}[/]?.*[/](META-INF[/].*|META-INF$)",
            Pattern.CASE_INSENSITIVE);

    private static final Pattern PATTERN_WEB_INF = Pattern.compile("[.]{2}[/]?.*[/](WEB-INF[/].*|WEB-INF$)",
            Pattern.CASE_INSENSITIVE);

    // ------------------------------------------------------------- Properties

    /**
     * Return descriptive information about this Valve implementation.
     */
    @Override
    public String getInfo() {
        return (info);

    }

    // --------------------------------------------------------- Public Methods

    /**
     * Cast to a StandardContext right away, as it will be needed later.
     *
     * @see org.apache.catalina.Contained#setContainer(org.apache.catalina.Container)
     */
    @Override
    public void setContainer(Container container) {
        super.setContainer(container);
        if (container instanceof StandardContext) {
            context = (StandardContext) container;
        }
    }

    /**
     * Select the appropriate child Wrapper to process this request,
     * based on the specified request URI.  If no matching Wrapper can
     * be found, return an appropriate HTTP error.
     *
     * @param request Request to be processed
     * @param response Response to be produced
     *
     * @exception IOException if an input/output error occurred
     * @exception ServletException if a servlet error occurred
     */
    @Override
    public int invoke(Request request, Response response)
        throws IOException, ServletException {

        Wrapper wrapper = preInvoke(request, response);
        if (wrapper == null) {
            return END_PIPELINE;
        }

        /* GlassFish 1343
        wrapper.getPipeline().invoke(request, response);
        */
        // START GlassFish 1343
        if (wrapper.getPipeline().hasNonBasicValves() ||
                wrapper.hasCustomPipeline()) {
            wrapper.getPipeline().invoke(request, response);
        } else {
            GlassFishValve basic = wrapper.getPipeline().getBasic();
            if (basic != null) {
                basic.invoke(request, response);
                basic.postInvoke(request, response);
            }
        }
        // END GlassFish 1343

        return END_PIPELINE;
    }

    /**
     * Tomcat style invocation.
     */
    @Override
    public void invoke(org.apache.catalina.connector.Request request,
                       org.apache.catalina.connector.Response response)
            throws IOException, ServletException {

        Wrapper wrapper = preInvoke(request, response);
        if (wrapper == null) {
            return;
        }

        /* GlassFish 1343
        wrapper.getPipeline().invoke(request, response);
        */
        // START GlassFish 1343
        if (wrapper.getPipeline().hasNonBasicValves() ||
                wrapper.hasCustomPipeline()) {
            wrapper.getPipeline().invoke(request, response);
        } else {
            GlassFishValve basic = wrapper.getPipeline().getBasic();
            if (basic != null) {
                basic.invoke(request, response);
                basic.postInvoke(request, response);
            }
        }
        // END GlassFish 1343

        postInvoke(request, response);
    }

    @Override
    public void postInvoke(Request request, Response response)
            throws IOException, ServletException {
    }

    /**
     * Report a "not found" error for the specified resource.  FIXME:  We
     * should really be using the error reporting settings for this web
     * application, but currently that code runs at the wrapper level rather
     * than the context level.
     *
     * @param response The response we are creating
     */
    private void notFound(HttpServletResponse response) {

        try {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        } catch (IllegalStateException | IOException e) {
            // Ignore
        }

    }

    /**
     * Log a message on the Logger associated with our Container (if any)
     *
     * @param message Message to be logged
     *
    private void log(String message) {
        org.apache.catalina.Logger logger = null;
        String containerName = null;
        if (container != null) {
            logger = container.getLogger();
            containerName = container.getName();
        }
        if (logger != null) {
            logger.log("StandardContextValve[" + container.getName() + "]: " +
                       message);
        } else {
            if (log.isLoggable(Level.INFO)) {
                log.info("StandardContextValve[" + containerName + "]: " +
                         message);
            }
        }
    }

    /**
     * Log a message on the Logger associated with our Container (if any)
     *
     * @param message Message to be logged
     * @param t Associated exception
     *
    private void log(String message, Throwable t) {
        org.apache.catalina.Logger logger = null;
        String containerName = null;
        if (container != null) {
            logger = container.getLogger();
            containerName = container.getName();
        }
        if (logger != null) {
            logger.log("StandardContextValve[" + container.getName() + "]: " +
                message, t, org.apache.catalina.Logger.WARNING);
        } else {
            log.log(Level.WARNING, "StandardContextValve[" + containerName +
                "]: " + message, t);
        }
    }
    */

    /**
     * resolves '.' and '..' elements in the path
     * if there are too many, making a path negative, returns null
     *
     * @param path to be normalized
     * @return normalized path or null
     */
    protected String normalize(String path) {
        if (path == null) {
            return null;
        }

        String rv = path;
        // starts with a double-slash
        if (rv.indexOf("//") == 0) {
            rv = rv.replace("//", "/");
        }
        // starts with dot-slash
        if (rv.indexOf("./") == 0) {
            rv = rv.replaceFirst("./", "/");
        }
        // has ../*/WEB-INF/* or ../*/META-INF/*
        if (PATTERN_WEB_INF.matcher(rv).find()) {
            return "/WEB-INF";
        }
        if (PATTERN_META_INF.matcher(rv).find()) {
            return "/META-INF";
        }

        // Normalize the slashes and add leading slash if necessary
        if (rv.indexOf('\\') >= 0) {
            rv = rv.replace('\\', '/');
        }

        // Resolve occurrences of "/../" in the normalized path
        while (true) {
            int idx = rv.indexOf("/../");
            if (idx < 0) {
                break;
            }
            if (idx == 0) {
                return null;  // negative relative path
            }
            int index2 = rv.lastIndexOf('/', idx - 1);
            rv = rv.substring(0, index2) + rv.substring(idx + 3);
        }

        // Resolve occurrences of "/./" example if the path looks like /app/./some/./something/./my.jsp
        //then after processing should look like /app/some/something/my.jsp
        rv = evaluateNormalizedPathWithSinglePoint(rv);

        //if the path don't start with / then include it
        if(!rv.startsWith("/")) {
            rv = "/" + rv;
        }

        // Return the normalized path that we have completed
        return rv;
    }

    /**
     * this method helps to evaluate the element "/./" on the path
     * @param path to be normalized
     * @return normalized path
     */
    private String evaluateNormalizedPathWithSinglePoint(String path) {
        // Resolve occurrences of "/./" in the normalized path
        while (true) {
            int idx = path.indexOf("/./");
            if (idx < 0) {
                break;
            }
            path = path.substring(0, idx) + path.substring(idx + 2);
        }
        return path;
    }

    private Wrapper preInvoke(Request request, Response response) {

        // Disallow any direct access to resources under WEB-INF or META-INF
        HttpRequest hreq = (HttpRequest) request;
        // START CR 6415120
        if (request.getCheckRestrictedResources()) {
        // END CR 6415120
            String requestPath = normalize(hreq.getRequestPathMB().toString(Charsets.UTF8_CHARSET));
            if ((requestPath == null)
                    || (requestPath.toUpperCase().startsWith("/META-INF/", 0))
                    || (requestPath.equalsIgnoreCase("/META-INF"))
                    || (requestPath.toUpperCase().startsWith("/WEB-INF/", 0))
                    || (requestPath.equalsIgnoreCase("/WEB-INF"))) {
                notFound((HttpServletResponse) response.getResponse());
                return null;
            }
        // START CR 6415120
        }
        // END CR 6415120

        // Wait if we are reloading
        boolean reloaded = false;
        while (((StandardContext) container).getPaused()) {
            reloaded = true;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // Ignore
            }
        }

        // Reloading will have stopped the old webappclassloader and
        // created a new one
        if (reloaded &&
                context.getLoader() != null &&
                context.getLoader().getClassLoader() != null) {
            Thread.currentThread().setContextClassLoader(
                    context.getLoader().getClassLoader());
        }

        // Select the Wrapper to be used for this Request
        Wrapper wrapper = request.getWrapper();
        if (wrapper == null) {
            notFound((HttpServletResponse) response.getResponse());
            return null;
        } else if (wrapper.isUnavailable()) {
            // May be as a result of a reload, try and find the new wrapper
            wrapper = (Wrapper) container.findChild(wrapper.getName());
            if (wrapper == null) {
                notFound((HttpServletResponse) response.getResponse());
                return null;
            }
        }

        return wrapper;
    }
}

Output:
【漏洞类型】路径遍历漏洞
【严重性】高
【漏洞摘要】原始代码在处理用户输入的请求路径时，使用正则表达式 `PATTERN_WEB_INF` 和 `PATTERN_META_INF` 来检测是否存在对 `/WEB-INF` 和 `/META-INF` 目录的非法访问尝试。然而，这些正则表达式不够严格，可能导致攻击者通过精心构造的路径绕过这些限制。具体来说，原始代码中的正则表达式只匹配 `../*/WEB-INF/*` 和 `../*/META-INF/*`，但没有匹配 `../WEB-INF` 或 `../META-INF` 这样的路径，这使得攻击者可以通过相对路径访问这些受保护的目录。
【关键代码模式】`normalize` 函数中使用正则表达式 `PATTERN_WEB_INF` 和 `PATTERN_META_INF` 检测路径中的非法访问尝试。
【修复方法说明】修复方法通过将正则表达式修改为更严格的形式，确保匹配 `../WEB-INF` 或 `../META-INF` 这样的路径，从而防止路径遍历攻击。