Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/alfio/manager/GroupManager.java
### 代码内容：
/**
 * This file is part of alf.io.
 *
 * alf.io is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alf.io is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with alf.io.  If not, see <http://www.gnu.org/licenses/>.
 */
package alfio.manager;

import alfio.model.Audit;
import alfio.model.Ticket;
import alfio.model.group.Group;
import alfio.model.group.GroupMember;
import alfio.model.group.LinkedGroup;
import alfio.model.modification.GroupMemberModification;
import alfio.model.modification.GroupModification;
import alfio.model.modification.LinkedGroupModification;
import alfio.model.result.ErrorCode;
import alfio.model.result.Result;
import alfio.repository.AuditingRepository;
import alfio.repository.GroupRepository;
import alfio.repository.TicketRepository;
import ch.digitalfondue.npjt.AffectedRowCountAndKey;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static alfio.model.group.LinkedGroup.MatchType.FULL;
import static alfio.model.group.LinkedGroup.Type.*;
import static java.util.Collections.singletonList;

@Component
@Log4j2
public class GroupManager {

    private final GroupRepository groupRepository;
    private final TicketRepository ticketRepository;
    private final AuditingRepository auditingRepository;
    private final TransactionTemplate requiresNewTransactionTemplate;

    public GroupManager(GroupRepository groupRepository,
                        TicketRepository ticketRepository,
                        AuditingRepository auditingRepository,
                        PlatformTransactionManager transactionManager) {
        this.groupRepository = groupRepository;
        this.ticketRepository = ticketRepository;
        this.auditingRepository = auditingRepository;
        this.requiresNewTransactionTemplate = new TransactionTemplate(transactionManager, new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
    }

    public Result<Integer> createNew(GroupModification input) {
        return requiresNewTransactionTemplate.execute(status -> {
            Group wl = createNew(input.getName(), input.getDescription(), input.getOrganizationId());
            Result<Integer> insertMembers = insertMembers(wl.getId(), input.getItems());
            if(!insertMembers.isSuccess()) {
                status.setRollbackOnly();
            }
            return insertMembers;
        });
    }

    Group createNew(String name, String description, int organizationId) {
        AffectedRowCountAndKey<Integer> insert = groupRepository.insert(name, description, organizationId);
        return groupRepository.getById(insert.getKey());
    }

    @Transactional
    public LinkedGroup createLink(int groupId,
                                  int eventId,
                                  LinkedGroupModification modification) {
        Objects.requireNonNull(groupRepository.getById(groupId), "Group not found");
        Validate.isTrue(modification.getType() != LIMITED_QUANTITY || modification.getMaxAllocation() != null, "Missing max allocation");
        AffectedRowCountAndKey<Integer> configuration = groupRepository.createConfiguration(groupId, eventId,
            modification.getTicketCategoryId(), modification.getType(), modification.getMatchType(), modification.getMaxAllocation());
        return groupRepository.getConfiguration(configuration.getKey());
    }

    @Transactional
    public LinkedGroup updateLink(int id, LinkedGroupModification modification) {
        LinkedGroup original = groupRepository.getConfigurationForUpdate(id);
        if(requiresCleanState(modification, original)) {
            Validate.isTrue(groupRepository.countWhitelistedTicketsForConfiguration(original.getId()) == 0, "Cannot update as there are already confirmed tickets.");
        }
        groupRepository.updateConfiguration(id, modification.getGroupId(), original.getEventId(), modification.getTicketCategoryId(), modification.getType(), modification.getMatchType(), modification.getMaxAllocation());
        return groupRepository.getConfiguration(id);
    }

    private boolean requiresCleanState(LinkedGroupModification modification, LinkedGroup original) {
        return (original.getType() == UNLIMITED && modification.getType() != UNLIMITED)
            || original.getGroupId() != modification.getGroupId()
            || (modification.getType() == LIMITED_QUANTITY && modification.getMaxAllocation() != null && original.getMaxAllocation() != null && modification.getMaxAllocation().compareTo(original.getMaxAllocation()) < 0);
    }

    boolean isGroupLinked(int eventId, int categoryId) {
        return CollectionUtils.isNotEmpty(findLinks(eventId, categoryId));
    }

    @Transactional(readOnly = true)
    public List<Group> getAllActiveForOrganization(int organizationId) {
        return groupRepository.getAllActiveForOrganization(organizationId);
    }

    @Transactional(readOnly = true)
    public List<Group> getAllForOrganization(int organizationId) {
        return groupRepository.getAllForOrganization(organizationId);
    }

    @Transactional
    public Optional<GroupModification> loadComplete(int id) {
        return groupRepository.getOptionalById(id)
            .map(wl -> {
                List<GroupMemberModification> items = groupRepository.getItems(wl.getId()).stream().map(i -> new GroupMemberModification(i.getId(), i.getValue(), i.getDescription())).collect(Collectors.toList());
                return new GroupModification(wl.getId(), wl.getName(), wl.getDescription(), wl.getOrganizationId(), items);
            });
    }

    @Transactional
    public Optional<Group> findById(int groupId, int organizationId) {
        return groupRepository.getOptionalById(groupId).filter(w -> w.getOrganizationId() == organizationId);
    }

    @Transactional
    public boolean isAllowed(String value, int eventId, int categoryId) {

        List<LinkedGroup> configurations = findLinks(eventId, categoryId);
        if(CollectionUtils.isEmpty(configurations)) {
            return true;
        }
        LinkedGroup configuration = configurations.get(0);
        return getMatchingMember(configuration, value).isPresent();
    }

    @Transactional
    public List<LinkedGroup> getLinksForEvent(int eventId) {
        return groupRepository.findActiveConfigurationsForEvent(eventId);
    }

    @Transactional
    public List<LinkedGroup> findLinks(int eventId, int categoryId) {
        return groupRepository.findActiveConfigurationsFor(eventId, categoryId);
    }

    Result<Integer> insertMembers(int groupId, List<GroupMemberModification> members) {

        Map<String, List<GroupMemberModification>> grouped = members.stream().collect(Collectors.groupingBy(GroupMemberModification::getValue));
        List<String> duplicates = grouped.entrySet().stream().filter(e -> e.getValue().size() > 1).map(Map.Entry::getKey).collect(Collectors.toList());

        return new Result.Builder<Integer>()
            .checkPrecondition(duplicates::isEmpty, ErrorCode.lazy(() -> ErrorCode.custom("value.duplicate", duplicates.stream().limit(10).collect(Collectors.joining(", ")))))
            .build(() -> Arrays.stream(groupRepository.insert(groupId, members)).sum());
    }

    @Transactional
    public boolean acquireMemberForTicket(Ticket ticket) {
        List<LinkedGroup> configurations = findLinks(ticket.getEventId(), ticket.getCategoryId());
        if(CollectionUtils.isEmpty(configurations)) {
            return true;
        }
        LinkedGroup configuration = configurations.get(0);
        Optional<GroupMember> optionalItem = getMatchingMember(configuration, ticket.getEmail());
        if(optionalItem.isEmpty()) {
            return false;
        }
        GroupMember item = optionalItem.get();
        boolean preventDuplication = configuration.getType() == ONCE_PER_VALUE;
        boolean limitAssignments = preventDuplication || configuration.getType() == LIMITED_QUANTITY;
        if(limitAssignments) {
            //reload and lock configuration
            configuration = groupRepository.getConfigurationForUpdate(configuration.getId());
            int existing = groupRepository.countExistingWhitelistedTickets(item.getId(), configuration.getId());
            int expected = preventDuplication ? 1 : Optional.ofNullable(configuration.getMaxAllocation()).orElse(0);
            if(existing >= expected) {
                return false;
            }
        }
        groupRepository.insertWhitelistedTicket(item.getId(), configuration.getId(), ticket.getId(), preventDuplication ? Boolean.TRUE : null);
        Map<String, Object> modifications = new HashMap<>();
        modifications.put("itemId", item.getId());
        modifications.put("configurationId", configuration.getId());
        modifications.put("ticketId", ticket.getId());
        auditingRepository.insert(ticket.getTicketsReservationId(), null, ticket.getEventId(), Audit.EventType.GROUP_MEMBER_ACQUIRED, new Date(), Audit.EntityType.TICKET, String.valueOf(ticket.getId()), singletonList(modifications));
        return true;
    }

    private Optional<GroupMember> getMatchingMember(LinkedGroup configuration, String email) {
        String trimmed = StringUtils.trimToEmpty(email);
        Optional<GroupMember> exactMatch = groupRepository.findItemByValueExactMatch(configuration.getGroupId(), trimmed);
        if(exactMatch.isPresent() || configuration.getMatchType() == FULL) {
            return exactMatch;
        }
        String partial = StringUtils.substringAfterLast(trimmed, "@");
        return partial.length() > 0 ? groupRepository.findItemEndsWith(configuration.getId(), configuration.getGroupId(), "%@"+partial) : Optional.empty();
    }

    @Transactional
    public void deleteWhitelistedTicketsForReservation(String reservationId) {
        List<Integer> tickets = ticketRepository.findTicketsInReservation(reservationId).stream().map(Ticket::getId).collect(Collectors.toList());
        if(!tickets.isEmpty()) {
            int result = groupRepository.deleteExistingWhitelistedTickets(tickets);
            log.trace("deleted {} whitelisted tickets for reservation {}", result, reservationId);
        }
    }

    @Transactional
    public void disableLink(int linkId) {
        Validate.isTrue(groupRepository.disableLink(linkId) == 1, "Error while disabling link");
    }

    @Transactional
    public Optional<GroupModification> update(int listId, GroupModification modification) {

        if(groupRepository.getOptionalById(listId).isEmpty() || CollectionUtils.isEmpty(modification.getItems())) {
            return Optional.empty();
        }

        List<String> existingValues = groupRepository.getAllValuesIncludingNotActive(listId);
        List<GroupMemberModification> notPresent = modification.getItems().stream()
            .filter(i -> i.getId() == null && !existingValues.contains(i.getValue().strip().toLowerCase()))
            .distinct()
            .collect(Collectors.toList());

        if(!notPresent.isEmpty()) {
            var insertResult = insertMembers(listId, notPresent);
            if(!insertResult.isSuccess()) {
                var error = Objects.requireNonNull(insertResult.getFirstErrorOrNull());
                throw new DuplicateGroupItemException(error.getDescription());
            }
        }
        groupRepository.update(listId, modification.getName(), modification.getDescription());
        return loadComplete(listId);
    }

    @Transactional
    public boolean deactivateMembers(List<Integer> memberIds, int groupId) {
        if(memberIds.isEmpty()) {
            return false;
        }
        groupRepository.deactivateGroupMember(memberIds, groupId);
        return true;
    }

    @Transactional
    public boolean deactivateGroup(int groupId) {
        List<Integer> members = groupRepository.getItems(groupId).stream().map(GroupMember::getId).collect(Collectors.toList());
        if(!members.isEmpty()) {
            Validate.isTrue(deactivateMembers(members, groupId), "error while disabling group members");
        }
        groupRepository.disableAllLinks(groupId);
        Validate.isTrue(groupRepository.deactivateGroup(groupId) == 1, "unexpected error while disabling group");
        return true;
    }

    @RequiredArgsConstructor
    public static class WhitelistValidator implements Predicate<WhitelistValidationItem> {

        private final int eventId;
        private final GroupManager groupManager;

        @Override
        public boolean test(WhitelistValidationItem item) {
            return groupManager.isAllowed(item.value, eventId, item.categoryId);
        }
    }

    @RequiredArgsConstructor
    public static class WhitelistValidationItem {
        private final int categoryId;
        private final String value;
    }

    public static class DuplicateGroupItemException extends RuntimeException {
        public DuplicateGroupItemException(String message) {
            super(message);
        }
    }
}

## 内容2
### 文件路径：
src/main/java/alfio/repository/GroupRepository.java
### 代码内容：
/**
 * This file is part of alf.io.
 *
 * alf.io is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alf.io is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with alf.io.  If not, see <http://www.gnu.org/licenses/>.
 */
package alfio.repository;

import alfio.model.group.Group;
import alfio.model.group.GroupMember;
import alfio.model.group.LinkedGroup;
import alfio.model.modification.GroupMemberModification;
import ch.digitalfondue.npjt.*;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@QueryRepository
public interface GroupRepository {

    String BY_EVENT_ID = "select * from group_link_active where event_id_fk = :eventId";

    @Query("insert into a_group(name, description, organization_id_fk) values(:name, :description, :orgId)")
    @AutoGeneratedKey("id")
    AffectedRowCountAndKey<Integer> insert(@Bind("name") String name,
                                           @Bind("description") String description,
                                           @Bind("orgId") int organizationId);

    @Query("select * from group_active where id = :id")
    Group getById(@Bind("id") int id);

    @Query("update a_group set name = :name, description = :description where id = :id")
    int update(@Bind("id") int id, @Bind("name") String name, @Bind("description") String description);

    @Query("select * from group_active where id = :id")
    Optional<Group> getOptionalById(@Bind("id") int id);

    @Query("select * from group_active where organization_id_fk = :organizationId order by name, description")
    List<Group> getAllActiveForOrganization(@Bind("organizationId") int organizationId);

    @Query("select * from a_group where organization_id_fk = :organizationId order by name, description")
    List<Group> getAllForOrganization(@Bind("organizationId") int organizationId);

    @Query("insert into group_link(a_group_id_fk, event_id_fk, ticket_category_id_fk, type, match_type, max_allocation)" +
        " values(:groupId, :eventId, :ticketCategoryId, :type, :matchType, :maxAllocation)")
    @AutoGeneratedKey("id")
    AffectedRowCountAndKey<Integer> createConfiguration(@Bind("groupId") int groupId,
                                                        @Bind("eventId") int eventId,
                                                        @Bind("ticketCategoryId") Integer ticketCategoryId,
                                                        @Bind("type") LinkedGroup.Type type,
                                                        @Bind("matchType") LinkedGroup.MatchType matchType,
                                                        @Bind("maxAllocation") Integer maxAllocation);

    default int[] insert(int groupId, List<GroupMemberModification> members) {
        MapSqlParameterSource[] params = members.stream()
            .map(i -> new MapSqlParameterSource("groupId", groupId).addValue("value", i.getValue().toLowerCase()).addValue("description", i.getDescription()))
            .toArray(MapSqlParameterSource[]::new);

        return getNamedParameterJdbcTemplate().batchUpdate("insert into group_member(a_group_id_fk, value, description) values(:groupId, :value, :description)", params);
    }

    NamedParameterJdbcTemplate getNamedParameterJdbcTemplate();

    @Query("select * from group_member_active where a_group_id_fk = :groupId order by value")
    List<GroupMember> getItems(@Bind("groupId") int groupId);

    @Query("select value from group_member where a_group_id_fk = :groupId order by value")
    List<String> getAllValuesIncludingNotActive(@Bind("groupId") int groupId);

    @Query("insert into whitelisted_ticket(group_member_id_fk, group_link_id_fk, ticket_id_fk, requires_unique_value)" +
        " values(:itemId, :configurationId, :ticketId, :requiresUniqueValue)")
    int insertWhitelistedTicket(@Bind("itemId") int itemId, @Bind("configurationId") int configurationId, @Bind("ticketId") int ticketId, @Bind("requiresUniqueValue") Boolean requiresUniqueValue);

    @Query(BY_EVENT_ID +
        " and ticket_category_id_fk = :categoryId" +
        " union all select * from group_link_active where event_id_fk = :eventId and ticket_category_id_fk is null")
    List<LinkedGroup> findActiveConfigurationsFor(@Bind("eventId") int eventId, @Bind("categoryId") int categoryId);

    @Query(BY_EVENT_ID)
    List<LinkedGroup> findActiveConfigurationsForEvent(@Bind("eventId") int eventId);

    @Query("select count(*) from group_link_active where event_id_fk = :eventId")
    Integer countByEventId(@Bind("eventId") int eventId);

    @Query("select * from group_link_active where id = :configurationId")
    LinkedGroup getConfiguration(@Bind("configurationId") int configurationId);

    @Query("select * from group_link where id = :configurationId for update")
    LinkedGroup getConfigurationForUpdate(@Bind("configurationId") int configurationId);

    @Query("select count(*) from whitelisted_ticket where group_link_id_fk = :configurationId")
    int countWhitelistedTicketsForConfiguration(@Bind("configurationId") int configurationId);

    @Query("update group_link set a_group_id_fk = :groupId, event_id_fk = :eventId, ticket_category_id_fk = :categoryId, type = :type, match_type = :matchType, max_allocation = :maxAllocation where id = :id")
    int updateConfiguration(@Bind("id") int configurationId,
                            @Bind("groupId") int groupId,
                            @Bind("eventId") int eventId,
                            @Bind("categoryId") Integer categoryId,
                            @Bind("type") LinkedGroup.Type type,
                            @Bind("matchType") LinkedGroup.MatchType matchType,
                            @Bind("maxAllocation") Integer maxAllocation);

    @Query("update group_link set active = false where id = :id")
    int disableLink(@Bind("id") int id);

    @Query("update group_link set active = false where a_group_id_fk = :groupId")
    int disableAllLinks(@Bind("groupId") int groupId);

    @Query("select * from group_member_active wi where wi.a_group_id_fk = :groupId and lower(wi.value) = lower(:value)")
    Optional<GroupMember> findItemByValueExactMatch(@Bind("groupId") int groupId, @Bind("value") String value);

    @Query("select * from group_member_active wi where wi.a_group_id_fk = :groupId and lower(wi.value) like lower(:value) limit 1")
    Optional<GroupMember> findItemEndsWith(@Bind("configurationId") int configurationId,
                                           @Bind("groupId") int groupId,
                                           @Bind("value") String value);

    @Query("select count(*) from whitelisted_ticket where group_member_id_fk = :itemId and group_link_id_fk = :configurationId")
    int countExistingWhitelistedTickets(@Bind("itemId") int itemId,
                                        @Bind("configurationId") int configurationId);

    @Query("delete from whitelisted_ticket where ticket_id_fk in (:ticketIds)")
    int deleteExistingWhitelistedTickets(@Bind("ticketIds") List<Integer> ticketIds);

    default void deactivateGroupMember(List<Integer> memberIds, int groupId) {
        MapSqlParameterSource[] params = memberIds.stream().map(memberId ->
                new MapSqlParameterSource("groupId", groupId)
                    .addValue("memberId", memberId)
                    .addValue("disabledPlaceholder", UUID.randomUUID().toString())
            ).toArray(MapSqlParameterSource[]::new);
        getNamedParameterJdbcTemplate().batchUpdate("update group_member set active = false, value = 'DISABLED-' || :disabledPlaceholder where id = :memberId and a_group_id_fk = :groupId", params);
    }

    @Query("update a_group set active = false where id = :groupId")
    int deactivateGroup(@Bind("groupId") int groupId);
}

## 内容3
### 文件路径：
src/main/webapp/resources/js/admin/service/service.js
### 代码内容：
(function () {
    "use strict";
    var baseServices = angular.module('adminServices', ['angular-growl' , 'ngAnimate']);

    baseServices.config(['$httpProvider', function($httpProvider) {
        var token = $("meta[name='_csrf']").attr("content");
        var header = $("meta[name='_csrf_header']").attr("content");
        $httpProvider.defaults.headers.post['X-Requested-With'] = 'XMLHttpRequest';
        $httpProvider.defaults.headers.post[header] = token;

        $httpProvider.defaults.headers.patch = angular.copy($httpProvider.defaults.headers.post);
        $httpProvider.defaults.headers.put = angular.copy($httpProvider.defaults.headers.post);
        $httpProvider.defaults.headers.delete = angular.copy($httpProvider.defaults.headers.post);

        $httpProvider.interceptors.push(['$rootScope', '$location', '$q', function($rootScope, $location, $q) {
            return {
                responseError: function(rejection) {//thanks to https://github.com/witoldsz/angular-http-auth/blob/master/src/http-auth-interceptor.js
                    var status = rejection.status;
                    if(status === 401) {
                        $rootScope.$emit('ErrorNotLoggedIn');
                        return false;
                    }
                    return $q.reject(rejection);
                }
            };
        }]);
    }]);

    baseServices.service('PaymentProxyService', function($http, HttpErrorHandler) {
        return {
            getAllProxies : function(orgId) {
                return $http.get('/admin/api/paymentProxies/'+orgId).error(HttpErrorHandler.handle);
            }
        };
    });

    baseServices.service('PurchaseContextService', function(EventService, SubscriptionService) {
        return {
            findAllReservations: function(type, contextName, page, search, status) {
                if(type === 'event') {
                    return EventService.findAllReservations(contextName, page, search, status);
                } else {
                    return SubscriptionService.findAllReservations(contextName, page, search, status);
                }
            }
        };
    })

    baseServices.service('EventService', function($http, HttpErrorHandler, $uibModal, $window, $rootScope, $q, LocationService, $timeout) {

        function copyGeoLocation(event) {
            event.latitude = event.geolocation.latitude;
            event.longitude = event.geolocation.longitude;
            event.zoneId = event.geolocation.timeZone;
        }

        var service = {
            data: {},
            getAllEvents : function() {
                return $http.get('/admin/api/events').error(HttpErrorHandler.handle);
            },
            getAllActiveEvents : function() {
                return $http.get('/admin/api/active-events').error(HttpErrorHandler.handle);
            },
            getAllExpiredEvents : function() {
                return $http.get('/admin/api/expired-events').error(HttpErrorHandler.handle);
            },
            getEvent: function(name) {
                return $http.get('/admin/api/events/'+name).success(function(result) {
                    $rootScope.$emit('EventLoaded', result.event);
                }).error(HttpErrorHandler.handle);
            },
            getEventById: function(eventId) {
                return $http.get('/admin/api/events/id/'+eventId).success(function(result) {
                    $rootScope.$emit('EventLoaded', result);
                }).error(HttpErrorHandler.handle);
            },
            checkEvent : function(event) {
                return $http['post']('/admin/api/events/check', event).error(HttpErrorHandler.handle);
            },
            createEvent : function(event) {
                copyGeoLocation(event);
                return $http['post']('/admin/api/events/new', event).error(HttpErrorHandler.handle);
            },
            toggleActivation: function(id, active) {
                return $http['put']('/admin/api/events/'+id+'/status?active='+active).error(HttpErrorHandler.handle);
            },
            updateEventHeader: function(eventHeader) {
                var update = function() {
                    return $http['post']('/admin/api/events/'+eventHeader.id+'/header/update', eventHeader).error(HttpErrorHandler.handle);
                };
                if(eventHeader.format === 'ONLINE') {
                    return update();
                }
                //
                if(eventHeader.geolocation && eventHeader.geolocation.latitude) {
                    copyGeoLocation(eventHeader);
                    //
                    return update();
                } else {
                    return LocationService.clientGeolocate(eventHeader.location).then(function(geo) {
                        eventHeader.latitude = geo.latitude;
                        eventHeader.longitude = geo.longitude;
                        eventHeader.zoneId = geo.timeZone;
                        return update();
                    })
                }

            },
            getTicketsForCategory: function(event, ticketCategory, page, search) {
              return $http.get('/admin/api/events/'+event.shortName+'/category/'+ticketCategory.id+'/ticket', {params: {page: page, search: search}}).error(HttpErrorHandler.handle);
            },
            updateEventPrices: function(eventPrices) {
                return $http['post']('/admin/api/events/'+eventPrices.id+'/prices/update', eventPrices).error(HttpErrorHandler.handle);
            },
            saveTicketCategory: function(event, ticketCategory) {
                var url = angular.isDefined(ticketCategory.id) ? ('/admin/api/events/' + event.id + '/categories/' + ticketCategory.id + '/update') : ('/admin/api/events/' + event.id + '/categories/new');
                return $http['post'](url, ticketCategory).error(HttpErrorHandler.handle);
            },
            toggleTicketLocking: function(event, ticket, category) {
                return $http['put']('/admin/api/events/' + event.shortName + '/categories/' + category.id + '/tickets/' + ticket.id +'/toggle-locking');
            },
            reallocateOrphans : function(srcCategory, targetCategoryId, eventId) {
                return $http['put']('/admin/api/events/reallocate', {
                    srcCategoryId: srcCategory.id,
                    targetCategoryId: targetCategoryId,
                    eventId: eventId
                }).error(HttpErrorHandler.handle);
            },
            deleteCategory: function(category, event) {

                var modal = $uibModal.open({
                    size:'md',
                    templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/angular-templates/admin/partials/event/fragment/delete-category-modal.html',
                    backdrop: 'static',
                    controller: function($scope) {
                        $scope.cancel = function() {
                            modal.dismiss('canceled');
                        };

                        $scope.deleteCategory = function() {
                            $http['delete']('/admin/api/events/'+event.shortName+'/category/'+category.id)
                                .error(HttpErrorHandler.handle)
                                .then(function() {
                                    modal.close('OK');
                                });
                        };
                        $scope.category = category;
                    }
                });
                return modal.result;
            },
            unbindTickets: function(event, category) {
                return $http['put']('/admin/api/events/'+event.shortName+'/category/'+category.id+'/unbind-tickets').error(HttpErrorHandler.handle);
            },
            getPendingPayments: function(eventName, forceReload) {
                service.data.pendingPayments = service.data.pendingPayments || {};
                var element = service.data.pendingPayments[eventName];
                var now = moment();
                if(!angular.isDefined(element) || now.subtract(20, 's').isAfter(element.ts) || forceReload) {
                    var promise = $http.get('/admin/api/events/'+eventName+'/pending-payments').error(HttpErrorHandler.handle);
                    element = {
                        ts: moment(),
                        payments: promise
                    };
                    service.data.pendingPayments[eventName] = element;
                }
                return element.payments;
            },
            getPendingPaymentsCount: function(eventName) {
                return $http.get('/admin/api/events/'+eventName+'/pending-payments-count').error(HttpErrorHandler.handle).then(function(res) {var v = parseInt(res.data); return isNaN(v) ? 0 : v; });
            },
            registerPayment: function(eventName, reservationId) {
                return $http['post']('/admin/api/events/'+eventName+'/pending-payments/'+reservationId+'/confirm').error(HttpErrorHandler.handle);
            },
            cancelPayment: function(eventName, reservationId, credit, notify) {
                return $http['delete']('/admin/api/events/'+eventName+'/pending-payments/'+reservationId, {
                    params: {
                        credit: credit,
                        notify: notify
                    }
                }).error(HttpErrorHandler.handle);
            },
            cancelMatchingPayment: function(eventName, reservationId, transactionId) {
                return $http['delete']('/admin/api/events/'+eventName+'/reservation/'+reservationId+'/transaction/'+transactionId+'/discard').error(HttpErrorHandler.handle);
            },
            sendCodesByEmail: function(eventName, categoryId, pairs) {
                return $http['post']('/admin/api/events/'+eventName+'/categories/'+categoryId+'/send-codes', pairs).error(HttpErrorHandler.handle);
            },
            loadSentCodes: function(eventName, categoryId) {
                return $http['get']('/admin/api/events/'+eventName+'/categories/'+categoryId+'/sent-codes').error(HttpErrorHandler.handle);
            },
            deleteRecipientData: function(eventName, categoryId, codeId) {
                return $http['delete']('/admin/api/events/'+eventName+'/categories/'+categoryId+'/codes/'+codeId+'/recipient').error(HttpErrorHandler.handle);
            },
            getSelectedLanguages: function(eventName) {
                return $http['get']('/admin/api/events/'+eventName+'/languages').error(HttpErrorHandler.handle);
            },
            getAllLanguages: function() {
                return $http['get']('/admin/api/events-all-languages').error(HttpErrorHandler.handle);
            },
            getSupportedLanguages: function() {
                return $http['get']('/admin/api/events-supported-languages').error(HttpErrorHandler.handle);
            },
            getDynamicFieldTemplates: function() {
                return $http['get']('/admin/api/event/additional-field/templates').error(HttpErrorHandler.handle);
            },
            getMessagesPreview: function(eventName, categoryId, messages) {
                var queryString = angular.isNumber(categoryId) ? '?categoryId='+categoryId : '';
                return $http['post']('/admin/api/events/'+eventName+'/messages/preview'+queryString, messages).error(HttpErrorHandler.handle);
            },
            sendMessages: function(eventName, categoryId, messages) {
                var queryString = angular.isDefined(categoryId) && categoryId !== "" && categoryId !== null ? '?categoryId='+categoryId : '';
                return $http['post']('/admin/api/events/'+eventName+'/messages/send'+queryString, messages).error(HttpErrorHandler.handle);
            },
            getFields : function(eventName) {
                return $http['get']('/admin/api/events/'+eventName+'/fields');
            },
            getAdditionalFields: function(eventName) {
                return $http.get('/admin/api/events/'+eventName+'/additional-field').error(HttpErrorHandler.handle);
            },
            getRestrictedValuesStats: function(eventName, id) {
                return $http.get('/admin/api/events/'+eventName+'/additional-field/'+id+'/stats').error(HttpErrorHandler.handle);
            },
            saveFieldDescription: function(eventName, fieldDescription) {
                return $http.post('/admin/api/events/'+eventName+'/additional-field/descriptions', fieldDescription);
            },
            addField: function(eventName, field) {
            	return $http.post('/admin/api/events/'+eventName+'/additional-field/new', field).error(HttpErrorHandler.handle);
            },
            updateField: function(eventName, toUpdate) {

                //new restrictedValues are complex objects, already present restrictedValues are plain string
                if(toUpdate && toUpdate.restrictedValues && toUpdate.restrictedValues.length > 0) {
                    var res = [];
                    for(var i = 0; i < toUpdate.restrictedValues.length; i++) {
                        res.push(toUpdate.restrictedValues[i].isNew ? toUpdate.restrictedValues[i].value: toUpdate.restrictedValues[i]);
                    }
                    toUpdate.restrictedValues = res;
                }
                //

                return $http['post']('/admin/api/events/'+eventName+'/additional-field/'+toUpdate.id, toUpdate);
            },
            deleteField: function(eventName, id) {
            	return $http['delete']('/admin/api/events/'+eventName+'/additional-field/'+id);
            },
            swapFieldPosition: function(eventName, id1, id2) {
            	return $http.post('/admin/api/events/'+eventName+'/additional-field/swap-position/'+id1+'/'+id2, null);
            },
            moveField: function(eventName, id, position) {
                return $http.post('/admin/api/events/'+eventName+'/additional-field/set-position/'+id, null, {
                    params: {
                        newPosition: position
                    }
                });
            },
            getAllReservationStatus : function(eventName) {
                return $http.get('/admin/api/reservation/event/'+eventName+'/reservations/all-status');
            },
            findAllReservations: function(eventName, page, search, status) {
                return $http.get('/admin/api/reservation/event/'+eventName+'/reservations/list', {params: {page: page, search: search, status: status}});
            },
            deleteEvent: function(event) {
                var modal = $uibModal.open({
                    size:'lg',
                    templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/angular-templates/admin/partials/event/fragment/delete-event-modal.html',
                    backdrop: 'static',
                    controller: function($scope) {
                        $scope.cancel = function() {
                            modal.dismiss('canceled');
                        };

                        $scope.deleteEvent = function() {
                            $http['delete']('/admin/api/events/'+event.id).then(function() {
                                modal.close('OK');
                            });
                        };
                        $scope.event = event;
                    }
                });
                return modal.result;
            },

            exportAttendees: function(event) {
                var modal = $uibModal.open({
                    size:'lg',
                    templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/angular-templates/admin/partials/event/fragment/select-field-modal.html',
                    backdrop: 'static',
                    controller: function($scope) {
                        $scope.selected = {};
                        $scope.format = 'excel';
                        service.getFields(event.shortName).then(function(fields) {
                            $scope.fields = fields.data;
                            angular.forEach(fields.data, function(v) {
                                $scope.selected[v.key] = false;
                            })
                        });

                        $scope.selectAll = function() {
                            angular.forEach($scope.selected, function(v,k) {
                                $scope.selected[k] = true;
                            });
                        };

                        $scope.deselectAll = function() {
                            angular.forEach($scope.selected, function(v,k) {
                                $scope.selected[k] = false;
                            });
                        };

                        $scope.download = function() {
                            var queryString = "format="+$scope.format+"&";
                            angular.forEach($scope.selected, function(v,k) {
                                if(v) {
                                    queryString+="fields="+k+"&";
                                }
                            });
                            var pathName = $window.location.pathname;
                            if(!pathName.endsWith("/")) {
                                pathName = pathName + "/";
                            }
                            $window.open(pathName+"api/events/"+event.shortName+"/export?"+queryString);
                        };
                    }
                });
            },
            removeTicketModal: function(event, reservationId, ticketId, invoiceRequested) {
                var deferred = $q.defer();
                var promise = deferred.promise;

                var modal = $uibModal.open({
                    size:'lg',
                    template:'<tickets-remove event="event" can-generate-credit-note="invoiceRequested" reservation-id="reservationId" ticket-id="ticketId" on-success="success(result)" on-cancel="close()"></tickets-remove>',
                    backdrop: 'static',
                    controller: function($scope) {
                        $scope.event = event;
                        $scope.ticketId = ticketId;
                        $scope.reservationId = reservationId;
                        $scope.invoiceRequested = invoiceRequested;
                        $scope.close = function() {
                            $scope.$dismiss(false);
                        };

                        $scope.success = function (result) {
                            $scope.$close(result);
                        }
                    }
                });
                return modal.result;
            },

            removeTickets: function(eventName, reservationId, ticketIds, ticketIdsToRefund, notify, issueCreditNote) {
                return $http.post('/admin/api/reservation/event/'+eventName+'/'+reservationId+'/remove-tickets', {
                    ticketIds: ticketIds,
                    refundTo: ticketIdsToRefund,
                    notify : notify,
                    issueCreditNote: issueCreditNote
                });
            },

            countInvoices: function(eventName) {
                return $http.get('/admin/api/events/'+eventName+'/invoices/count').error(HttpErrorHandler.handle);
            },

            getTicketsStatistics: function(eventName, from, to) {
                return $http.get('/admin/api/events/'+eventName+'/ticket-sold-statistics', {params: {from: from, to: to}});
            },

            rearrangeCategories: function(event) {
                var modal = $uibModal.open({
                    size:'lg',
                    templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/angular-templates/admin/partials/event/rearrange-categories.html',
                    backdrop: 'static',
                    controller: function($scope) {
                        var ctrl = this;
                        ctrl.event = event;
                        var setOrdinal = function(categories) {
                            for(var i=0, o=1; i < categories.length; i++, o++) {
                                var category = categories[i];
                                category.ordinal = o;
                            }
                            return categories;
                        };
                        ctrl.categories = event.ticketCategories.map(function(category) {
                            return {
                                id: category.id,
                                name: category.name,
                                ordinal: category.ordinal
                            };
                        });
                        ctrl.sortedCategories = setOrdinal(_.sortByAll(ctrl.categories, ['ordinal', 'formattedInception', 'id']));
                        $scope.$on('categories-bag.drop', function (e, el) {
                            $timeout(function() {
                                ctrl.sortedCategories = setOrdinal(ctrl.sortedCategories);
                            }, 10);
                        });
                        ctrl.swap = function(index, category, up) {
                            var list = ctrl.sortedCategories.slice();
                            var target = up ? index - 1 : index + 1;
                            var toBeSwapped = list[target];
                            list[target] = category;
                            list[index] = toBeSwapped;
                            ctrl.sortedCategories.length = 0;
                            for(var i=0; i<list.length; i++) {
                                ctrl.sortedCategories.push(list[i]);
                            }
                            setOrdinal(ctrl.sortedCategories);
                        };
                        ctrl.save = function() {
                            $scope.$close(ctrl.sortedCategories);
                        };
                        ctrl.dismiss = function() {
                            $scope.$dismiss(false);
                        }

                    },
                    controllerAs:'$ctrl'
                });
                return modal.result.then(function(categories) {
                    return $http.put('/admin/api/events/'+event.shortName+'/rearrange-categories', categories).error(HttpErrorHandler.handle);
                });
            },

            createAdditionalService: function(eventId, additionalService) {
                return $http.post('/admin/api/event/'+eventId+'/additional-services/', additionalService).error(HttpErrorHandler.handle);
            },

            updateEventMetadata: function(eventName, metadata) {
                return $http.put('/admin/api/events/'+eventName+'/metadata', metadata).error(HttpErrorHandler.handle);
            },
            retrieveMetadata: function(eventName) {
                return $http.get('/admin/api/events/'+eventName+'/metadata').error(HttpErrorHandler.handle);
            },

            updateCategoryMetadata: function(eventName, categoryId, metadata) {
                return $http.put('/admin/api/events/'+eventName+'/category/'+categoryId+'/metadata', metadata).error(HttpErrorHandler.handle);
            },
            retrieveCategoryMetadata: function(eventName, categoryId) {
                return $http.get('/admin/api/events/'+eventName+'/category/'+categoryId+'/metadata').error(HttpErrorHandler.handle);
            },
            executeCapability: function(eventName, capability, parameters) {
                return $http.post('/admin/api/events/'+eventName+'/capability/'+capability, parameters);
            }
        };
        return service;
    });

    baseServices.service("LocationService", function($http, $q, HttpErrorHandler) {

        var reqCounter = 0;

        function getMapUrl(latitude, longitude) {
            return $http.get('/admin/api/location/static-map-image', {params: {lat: latitude, lng: longitude}}).then(function(res) {
                return res.data;
            });
        }

        function handleGoogleGeolocate(location, locService, apiKeyAndProvider, resolve, reject) {
            var key = apiKeyAndProvider.keys['MAPS_CLIENT_API_KEY'];

            var keyParam = key ? ('&key='+encodeURIComponent(key)) : '';

            if(!window.google || !window.google.maps) {

                reqCounter++;

                var script = document.createElement('script');

                var callBackName = 'clientGeolocate'+reqCounter;

                script.src = 'https://maps.googleapis.com/maps/api/js?libraries=places&callback='+callBackName+keyParam;
                document.head.appendChild(script);
                window[callBackName] = function() {
                    search();
                }
            } else {
                search();
            }

            function search() {
                var geocoder = new window.google.maps.Geocoder();
                geocoder.geocode({'address': location}, function(results, status) {
                    if (status === 'OK') {
                        var ret = {};
                        ret.latitude = ""+results[0].geometry.location.lat()
                        ret.longitude = ""+results[0].geometry.location.lng()
                        $q.all([getMapUrl(ret.latitude, ret.longitude), locService.getTimezone(ret.latitude, ret.longitude)]).then(function(success) {
                            ret.mapUrl = success[0];
                            var tz = success[1];
                            if(tz.data) {
                                ret.timeZone = tz.data;
                            }
                            resolve(ret);
                        }, function () {
                            reject();
                        })

                    } else {
                        reject();
                    }
                });
            }
        }

        function handleHEREGeolocate(location, locService, apiKeyAndProvider, resolve, reject) {
            var apiKey = apiKeyAndProvider.keys['MAPS_HERE_API_KEY'];
            $http.get('https://geocoder.ls.hereapi.com/6.2/geocode.json', {params: {apiKey: apiKey, searchtext: location}}).then(function(res) {
                var view = res.data.Response.View;
                if(view.length > 0 && view[0].Result.length > 0 && view[0].Result[0].Location) {
                    var location = view[0].Result[0].Location;
                    var pos = location.DisplayPosition;
                    var ret = {latitude: pos.Latitude, longitude: pos.Longitude};

                    $q.all([getMapUrl(ret.latitude, ret.longitude), locService.getTimezone(ret.latitude, ret.longitude)]).then(function(success) {
                        ret.mapUrl = success[0];
                        var tz = success[1];
                        if(tz.data) {
                            ret.timeZone = tz.data;
                        }
                        resolve(ret);
                    }, function () {
                        reject();
                    })
                } else {
                    reject();
                }

            }, function () {
                reject();
            })
        }

        return {
            mapApiKey: function() {
                return $http.get('/admin/api/location/map-provider-client-api-key').then(function(res) {
                    return res.data;
                });
            },
            clientGeolocate: function(location) {
                var locService = this;
                return $q(function(resolve, reject) {

                    locService.mapApiKey().then(function(apiKeyAndProvider) {

                        if(apiKeyAndProvider.provider === 'GOOGLE') {
                            handleGoogleGeolocate(location, locService, apiKeyAndProvider, resolve, reject);
                        } else if (apiKeyAndProvider.provider === 'HERE') {
                            handleHEREGeolocate(location, locService, apiKeyAndProvider, resolve, reject);
                        } else {
                            resolve({latitude: null, longitude: null});
                        }
                    })

                });
            },
            getTimezone : function(latitude, longitude) {
              return $http.get('/admin/api/location/timezone', {params: {lat: latitude, lng: longitude}});
            },
            getTimezones: function() {
                return $http.get('/admin/api/location/timezones');
            },
            getMapUrl : getMapUrl
        };
    });

    baseServices.service('ValidationService', function(NotificationHandler) {
        return {
            validationPerformer: function($q, validator, data, form) {
                var deferred = $q.defer();
                validator(data).success(this.validationResultHandler(form, deferred)).error(function(error) {
                    deferred.reject(error);
                });
                return deferred.promise;
            },
            validationResultHandler: function(form, deferred) {
                return function(validationResult) {
                    if(validationResult.errorCount > 0) {
                        if(form.$setError) {
                            angular.forEach(validationResult.validationErrors, function(error) {
                                form.$setError(error.fieldName, error.message);
                            });
                        } else if (form.$setValidity) {
                            angular.forEach(validationResult.validationErrors, function(error) {
                                form[error.fieldName].$setValidity(error.code, false);
                            });
                        } else {
                            var firstError = validationResult.validationErrors[0];
                            NotificationHandler.showError(firstError.description);
                        }
                        deferred.reject('invalid form');
                    }
                    deferred.resolve();
                };
            }
        }
    });

    baseServices.service("HttpErrorHandler", ['$log', 'NotificationHandler', function($log, NotificationHandler) {
        var getMessage = function(body, status) {
            switch(status) {
                case 400:
                    return 'Malformed Request';
                case 404:
                    return 'Resource not found';
                case 403:
                    return 'Your account is not authorized to perform this operation.';
                case 500:
                    return 'Internal Server Error: ' + body;
                default:
                    return 'Connection Error';
            }
        };
        return {
            handle : function(body, status) {
                var message = getMessage(body, status);
                $log.warn(message, status, body);
                NotificationHandler.showError(message);
            }
        };
    }]);

    baseServices.service("NotificationHandler", ["growl", function (growl) {
        var config = {ttl: 5000, disableCountDown: true};
        return {
            showSuccess: function (message) {
                return growl.success(message, config);
            },
            showWarning: function (message) {
                return growl.warning(message, config);
            },
            showInfo : function (message) {
                return growl.info(message, config);
            },
            showError : function (message) {
                return growl.error(message, config);
            }
        }

    }]);

    baseServices.service("PriceCalculator", function() {
        var instance = {
            calculateTotalPrice: function(event, viewMode) {
                if(isNaN(event.regularPrice) || isNaN(event.vatPercentage)) {
                    return '0.00';
                }
                var vat = numeral(0.0);
                if((viewMode && angular.isDefined(event.id)) || !event.vatIncluded) {
                    vat = instance.applyPercentage(event.regularPrice, event.vatPercentage);
                }
                return numeral(vat.add(event.regularPrice).format('0.00')).value();
            },
            calcBarValue: function(category) {
                if(category.bounded) {
                    return category.maxTickets || 1;
                }
                return 0;
            },
            calcCategoryPricePercent: function(category, event, editMode) {
                if(isNaN(event.regularPrice) || isNaN(category.price)) {
                    return '0.00';
                }
                return instance.calcPercentage(category.price, event.regularPrice).format('0.00');
            },
            calcCategoryPrice: function(category, event) {
                if(isNaN(event.vatPercentage) || isNaN(category.price)) {
                    return '0.00';
                }
                var vat = numeral(0.0);
                if(event.vatIncluded) {
                    vat = instance.applyPercentage(category.price, event.vatPercentage);
                }
                return numeral(category.price).add(vat).format('0.00');
            },
            calcPercentage: function(fraction, total) {
                if(isNaN(fraction) || isNaN(total)){
                    return numeral(0.0);
                }
                return numeral(numeral(fraction).divide(total).multiply(100).format('0.00'));
            },
            applyPercentage: function(total, percentage) {
                return numeral(numeral(percentage).divide(100).multiply(total).format('0.00'));
            }
        };
        return instance;
    });

    baseServices.service("PromoCodeService", function($http, HttpErrorHandler) {

        function addUtfOffsetIfNecessary(promoCode) {
            if(promoCode.eventId == null) {
                promoCode.utcOffset = (new Date()).getTimezoneOffset()*-60; //in seconds
            }
        }

        return {
                add : function(promoCode) {
                    addUtfOffsetIfNecessary(promoCode);
                    return $http['post']('/admin/api/promo-code', promoCode).error(HttpErrorHandler.handle);
                },
                remove: function(promoCodeId) {
                    return $http['delete']('/admin/api/promo-code/' + promoCodeId).error(HttpErrorHandler.handle);
                },
                list: function(eventId) {
                    return $http.get('/admin/api/events/' + eventId + '/promo-code').error(HttpErrorHandler.handle);
                },
                listOrganization : function(organizationId) {
                    return $http.get('/admin/api/organization/' + organizationId + '/promo-code').error(HttpErrorHandler.handle);
                },
                countUse : function(promoCodeId) {
                    return $http.get('/admin/api/promo-code/' + promoCodeId + '/count-use');
                },
                disable: function(promoCodeId) {
                    return $http['post']('/admin/api/promo-code/' + promoCodeId + '/disable');
                },
                update: function(promoCodeId, toUpdate) {
                    addUtfOffsetIfNecessary(toUpdate);
                    return $http.post('/admin/api/promo-code/' + promoCodeId, toUpdate);
                },
                getUsageDetails: function(promoCodeId, eventShortName) {
                    return $http.get('/admin/api/promo-code/' + promoCodeId + '/detailed-usage', {
                        params: {
                            eventShortName
                        }
                    });
                }
        };
    });

    baseServices.service("CheckInService", ['$http', 'HttpErrorHandler', '$window', function($http, HttpErrorHandler, $window) {
        return {
            findAllTicketIds : function(eventId) {
                var sessionStorageKey = 'CHECK_IN_TIMESTAMP_'+eventId;
                var since = $window.sessionStorage.getItem(sessionStorageKey);
                var sinceParam = since ? '?changedSince='+since : '';
                return $http.get('/admin/api/check-in/' + eventId + '/ticket-identifiers' + sinceParam)
                    .then(function(resp) {
                        $window.sessionStorage.setItem(sessionStorageKey, resp.headers('Alfio-TIME'));
                        return resp;
                    }, HttpErrorHandler.handle);
            },

            downloadTickets: function(eventId, ids) {
                return $http.post('/admin/api/check-in/'+eventId+'/tickets', ids).error(HttpErrorHandler.handle);
            },

            getTicket: function(eventId, code) {
                var ticketIdentifier = code.split('/')[0];
                return $http.get('/admin/api/check-in/' + eventId + '/ticket/' + ticketIdentifier + "?qrCode=" + encodeURIComponent(code)).error(HttpErrorHandler.handle);
            },

            checkIn: function(eventId, ticket) {
                var ticketIdentifier = ticket.code.split('/')[0];
                return $http['post']('/admin/api/check-in/' + eventId + '/ticket/' + ticketIdentifier, ticket).error(HttpErrorHandler.handle);
            },

            manualCheckIn: function(ticket) {
                return $http['post']('/admin/api/check-in/' + ticket.eventId + '/ticket/' + ticket.uuid + '/manual-check-in', ticket).error(HttpErrorHandler.handle);
            },

            revertCheckIn: function(ticket) {
                return $http['post']('/admin/api/check-in/' + ticket.eventId + '/ticket/' + ticket.uuid + '/revert-check-in', ticket).error(HttpErrorHandler.handle);
            },

            confirmPayment: function(eventId, ticket) {
                var ticketIdentifier = ticket.code.split('/')[0];
                return $http['post']('/admin/api/check-in/' + eventId + '/ticket/' + ticketIdentifier + '/confirm-on-site-payment').error(HttpErrorHandler.handle);
            }
        };
    }]);

    baseServices.service("FileUploadService", function($http, HttpErrorHandler) {
        return {
            upload : function(file) {
                return $http['post']('/admin/api/file/upload', file).error(HttpErrorHandler.handle);
            },

            uploadImageWithResize: function(file) {
                return $http['post']('/admin/api/file/upload?resizeImage=true', file).error(HttpErrorHandler.handle);
            }
        };
    });

    baseServices.service('WaitingQueueService', ['$http', 'HttpErrorHandler', function($http, HttpErrorHandler) {
        return {
            getWaitingQueueStatus: function(eventName) {
                return $http.get('/admin/api/event/'+eventName+'/waiting-queue/status').error(HttpErrorHandler.handle);
            },
            setPaused: function(eventName, status) {
                return $http.put('/admin/api/event/'+eventName+'/waiting-queue/status', {status: status}).error(HttpErrorHandler.handle);
            },
            countSubscribers: function(eventName) {
                return $http.get('/admin/api/event/'+eventName+'/waiting-queue/count').error(HttpErrorHandler.handle);
            },
            loadAllSubscribers: function(eventName) {
                return $http.get('/admin/api/event/'+eventName+'/waiting-queue/load').error(HttpErrorHandler.handle);
            },
            removeSubscriber: function(eventName, subscriber) {
                return $http['delete']('/admin/api/event/'+eventName+'/waiting-queue/subscriber/'+subscriber.id).error(HttpErrorHandler.handle);
            },
            restoreSubscriber: function(eventName, subscriber) {
                return $http['put']('/admin/api/event/'+eventName+'/waiting-queue/subscriber/'+subscriber.id+'/restore').error(HttpErrorHandler.handle);
            }
        };
    }]);

    baseServices.service('UtilsService', ['$http', 'HttpErrorHandler', function($http, HttpErrorHandler) {
        return {
            generateShortName: function(displayName) {
                return $http.get('/admin/api/utils/short-name/generate?displayName='+displayName).error(HttpErrorHandler.handle);
            },
            validateShortName: function(shortName) {
                return $http['post']('/admin/api/utils/short-name/validate', null, {params: {shortName: shortName}}).error(HttpErrorHandler.handle);
            },
            renderCommonMark: function(text) {
            	return $http.get('/admin/api/utils/render-commonmark', {params: {text: text}}).error(HttpErrorHandler.handle);
            },
            getApplicationInfo: function() {
                return $http.get('/admin/api/utils/alfio/info').error(HttpErrorHandler.handle);
            },
            getAvailableCurrencies: function() {
                return $http.get('/admin/api/utils/currencies').error(HttpErrorHandler.handle);
            },
            logout: function() {
                return $http.post("/logout", {}).error(HttpErrorHandler.handle);
            },
            getFirstTranslation: function(localized) {
                var keys = Object.keys(localized);
                if(keys.length > 0) {
                    return localized[keys[0]];
                }
                return null;
            }
        };
    }]);

    baseServices.service('CountriesService', ['$http', 'HttpErrorHandler', '$q', function($http, HttpErrorHandler) {
        var request = $http.get('/admin/api/utils/countriesForVat').then(function(res) {
            return res.data;
        }, HttpErrorHandler.handle);
        return {
            getCountries: function() {
                return request;
            },

            getDescription: function(countryCode) {
                return request.then(function(countries) {
                    return countries[countryCode] || countryCode;
                });
            }

        };
    }]);

    baseServices.service('MenuButtonService', ['EventService', '$window', '$uibModal', 'NotificationHandler', function(EventService, $window, $uibModal, NotificationHandler) {
        return {
            configureMenu: function(ctrl) {
                ctrl.openFieldSelectionModal = function() {
                    EventService.exportAttendees(ctrl.event);
                };
                ctrl.downloadSponsorsScan = function() {
                    var pathName = $window.location.pathname;
                    if(!pathName.endsWith("/")) {
                        pathName = pathName + "/";
                    }
                    $window.open(pathName+"api/events/"+ctrl.event.shortName+"/sponsor-scan/export");
                };
                ctrl.openWaitingQueueModal = function() {
                    var outCtrl = ctrl;
                    var modal = $uibModal.open({
                        size:'lg',
                        templateUrl: window.ALFIO_CONTEXT_PATH + '/resources/angular-templates/admin/partials/event/fragment/download-waiting-queue.html',
                        backdrop: 'static',
                        controllerAs: 'ctrl',
                        controller: function($scope) {
                            var ctrl = this;
                            $scope.format = 'excel';

                            $scope.download = function() {
                                var queryString = "format="+$scope.format;
                                var pathName = $window.location.pathname;
                                if(!pathName.endsWith("/")) {
                                    pathName = pathName + "/";
                                }
                                $window.open(pathName+"api/event/" + outCtrl.event.shortName + "/waiting-queue/download?"+queryString);
                            };

                            ctrl.close = function() {
                                modal.close();
                            }
                        }
                    });
                };
                ctrl.downloadInvoices = function(type) {
                    EventService.countInvoices(ctrl.event.shortName).then(function (res) {
                        var count = res.data;
                        if(count > 0) {
                            var pathName = $window.location.pathname;
                            if(!pathName.endsWith("/")) {
                                pathName = pathName + "/";
                            }
                            var suffix = '';
                            if(type === 'xls') {
                                suffix = '-xls';
                            }
                            $window.open(pathName+"api/events/"+ctrl.event.shortName+"/all-documents"+suffix);
                        } else {
                            NotificationHandler.showInfo("No invoices have been found.");
                        }
                    });
                };
            }
        }
    }]);

    baseServices.service('ImageTransformService', ['FileUploadService', '$window', '$q', function(FileUploadService, $window, $q) {
        return {
            transformAndUploadImages: function(files) {
                var deferred = $q.defer();
                var reader = new FileReader();
                reader.onload = function(e) {
                    var imageBase64 = e.target.result;
                    var fileType = files[0].type;
                    var fileName = files[0].name;
                    var fileContent = imageBase64.substring(imageBase64.indexOf('base64,') + 7);
                    if (fileType=== 'image/svg+xml') {
                        var img = new Image();
                        var fromSvgToPng = function(image) {
                            var cnv = document.createElement('canvas');
                            cnv.width = image.width;
                            cnv.height = image.height;
                            var canvasCtx = cnv.getContext('2d');
                            canvasCtx.drawImage(image, 0, 0);
                            var imgData = cnv.toDataURL('image/png');
                            img.remove();
                            fileType = "image/png";
                            fileName = fileName+".png";
                            fileContent = imgData.substring(imgData.indexOf('base64,') + 7);
                            FileUploadService.uploadImageWithResize({file : fileContent, type : fileType, name : fileName}).then(function(res) {
                                deferred.resolve({
                                    imageBase64: imgData,
                                    fileBlobId: res.data
                                });
                            }, function(err) {
                                deferred.reject(null); // error is already notified by the NotificationService
                            });
                        };
                        var parser = new DOMParser();
                        var svgRoot = parser.parseFromString(atob(fileContent), 'text/xml').getElementsByTagName("svg")[0];
                        if (svgRoot.hasAttribute('height')) {
                            img.height = svgRoot.getAttribute('height');
                            img.width = svgRoot.getAttribute('width');
                        } else {
                            img.height = 500;
                        }
                        img.setAttribute('aria-hidden', 'true');
                        img.style.position = 'absolute';
                        img.style.top = '-10000px';
                        img.style.left = '-10000px';
                        img.onload = function() {
                            // see FF limitation https://stackoverflow.com/a/61195034
                            // we need to set in a explicit way the size _inside_ the svg
                            svgRoot.setAttribute('width', img.width+'px');
                            svgRoot.setAttribute('height', img.height+'px');
                            var serializedSvg = new XMLSerializer().serializeToString(svgRoot);
                            img.onload = function() {
                                fromSvgToPng(img);
                            }
                            img.src = 'data:image/svg+xml;base64,'+btoa(serializedSvg);
                        };
                        $window.document.body.appendChild(img);
                        img.src = imageBase64;
                    } else {
                        FileUploadService.uploadImageWithResize({file : fileContent, type : fileType, name : fileName}).then(function(res) {
                            deferred.resolve({
                                imageBase64: imageBase64,
                                fileBlobId: res.data
                            });
                        }, function(err) {
                            deferred.reject(null); // error is already notified by the NotificationService
                        });
                    }
                };
                if (files.length <= 0) {
                    deferred.reject('Your image was not uploaded correctly.Please upload the image again');
                } else if (!((files[0].type === 'image/png') || (files[0].type === 'image/jpeg') || (files[0].type === 'image/gif') || (files[0].type === 'image/svg+xml'))) {
                    deferred.reject('Only PNG, JPG, GIF or SVG image files are accepted');
                } else if (files[0].size > (1024 * 200)) {
                    deferred.reject('Image size exceeds the allowable limit 200KB');
                } else {
                    reader.readAsDataURL(files[0]);
                }
                return deferred.promise;
            }
        };
    }])
})();

Output:
【漏洞类型】跨站脚本攻击 (XSS)
【严重性】High
【漏洞摘要】此代码存在跨站脚本攻击 (XSS) 漏洞。具体表现为未对用户输入（如组名称和描述）进行适当的 HTML 转义，可能导致恶意用户输入的脚本被注入并执行。根本原因在于业务逻辑中对用户输入的直接使用，而未进行安全处理。
【关键代码模式】未使用 `StringEscapeUtils.escapeHtml4()` 对用户输入进行转义。
【修复方法说明】修复方法通过使用 `StringEscapeUtils.escapeHtml4()` 对用户输入进行 HTML 转义，防止恶意脚本注入。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
core/src/main/java/apoc/export/csv/ExportCSV.java
### 代码内容：
package apoc.export.csv;

import apoc.ApocConfig;
import apoc.Pools;
import apoc.export.cypher.ExportFileManager;
import apoc.export.cypher.FileManagerFactory;
import apoc.export.util.ExportConfig;
import apoc.export.util.ExportUtils;
import apoc.export.util.NodesAndRelsSubGraph;
import apoc.export.util.ProgressReporter;
import apoc.result.ProgressInfo;
import apoc.util.Util;
import org.neo4j.cypher.export.DatabaseSubGraph;
import org.neo4j.cypher.export.SubGraph;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Result;
import org.neo4j.graphdb.Transaction;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
import org.neo4j.procedure.TerminationGuard;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * @author mh
 * @since 22.05.16
 */
public class ExportCSV {
    @Context
    public Transaction tx;

    @Context
    public GraphDatabaseService db;

    @Context
    public TerminationGuard terminationGuard;

    @Context
    public ApocConfig apocConfig;

    @Context
    public Pools pools;

    public ExportCSV() {
    }

    @Procedure
    @Description("apoc.export.csv.all(file,config) - exports whole database as csv to the provided file")
    public Stream<ProgressInfo> all(@Name("file") String fileName, @Name("config") Map<String, Object> config) throws Exception {
        String source = String.format("database: nodes(%d), rels(%d)", Util.nodeCount(tx), Util.relCount(tx));
        return exportCsv(fileName, source, new DatabaseSubGraph(tx), new ExportConfig(config));
    }

    @Procedure
    @Description("apoc.export.csv.data(nodes,rels,file,config) - exports given nodes and relationships as csv to the provided file")
    public Stream<ProgressInfo> data(@Name("nodes") List<Node> nodes, @Name("rels") List<Relationship> rels, @Name("file") String fileName, @Name("config") Map<String, Object> config) throws Exception {
        ExportConfig exportConfig = new ExportConfig(config);
        preventBulkImport(exportConfig);
        String source = String.format("data: nodes(%d), rels(%d)", nodes.size(), rels.size());
        return exportCsv(fileName, source, new NodesAndRelsSubGraph(tx, nodes, rels), exportConfig);
    }
    @Procedure
    @Description("apoc.export.csv.graph(graph,file,config) - exports given graph object as csv to the provided file")
    public Stream<ProgressInfo> graph(@Name("graph") Map<String,Object> graph, @Name("file") String fileName, @Name("config") Map<String, Object> config) throws Exception {
        Collection<Node> nodes = (Collection<Node>) graph.get("nodes");
        Collection<Relationship> rels = (Collection<Relationship>) graph.get("relationships");
        String source = String.format("graph: nodes(%d), rels(%d)", nodes.size(), rels.size());
        return exportCsv(fileName, source, new NodesAndRelsSubGraph(tx, nodes, rels), new ExportConfig(config));
    }

    @Procedure
    @Description("apoc.export.csv.query(query,file,{config,...,params:{params}}) - exports results from the cypher statement as csv to the provided file")
    public Stream<ProgressInfo> query(@Name("query") String query, @Name("file") String fileName, @Name("config") Map<String, Object> config) throws Exception {
        ExportConfig exportConfig = new ExportConfig(config);
        preventBulkImport(exportConfig);
        Map<String,Object> params = config == null ? Collections.emptyMap() : (Map<String,Object>)config.getOrDefault("params", Collections.emptyMap());
        Result result = tx.execute(query,params);

        String source = String.format("statement: cols(%d)", result.columns().size());
        return exportCsv(fileName, source,result, exportConfig);
    }

    private void preventBulkImport(ExportConfig config) {
        if (config.isBulkImport()) {
            throw new RuntimeException("You can use the `bulkImport` only with apoc.export.all and apoc.export.csv.graph");
        }
    }

    private Stream<ProgressInfo> exportCsv(@Name("file") String fileName, String source, Object data, ExportConfig exportConfig) throws Exception {
        apocConfig.checkWriteAllowed(exportConfig, fileName);
        final String format = "csv";
        ProgressInfo progressInfo = new ProgressInfo(fileName, source, format);
        progressInfo.batchSize = exportConfig.getBatchSize();
        ProgressReporter reporter = new ProgressReporter(null, null, progressInfo);
        CsvFormat exporter = new CsvFormat(db);

        ExportFileManager cypherFileManager = FileManagerFactory
                .createFileManager(fileName, exportConfig.isBulkImport(), exportConfig);

        if (exportConfig.streamStatements()) {
            return ExportUtils.getProgressInfoStream(db, pools.getDefaultExecutorService(), terminationGuard, format, exportConfig, reporter, cypherFileManager,
                    (reporterWithConsumer) -> dump(data, exportConfig, reporterWithConsumer, cypherFileManager, exporter));
        } else {
            dump(data, exportConfig, reporter, cypherFileManager, exporter);
            return reporter.stream();
        }
    }

    private void dump(Object data, ExportConfig c, ProgressReporter reporter, ExportFileManager printWriter, CsvFormat exporter) {
        if (data instanceof SubGraph)
            exporter.dump((SubGraph)data,printWriter,reporter,c);
        if (data instanceof Result)
            exporter.dump((Result)data,printWriter,reporter,c);
    }
}

## 内容2
### 文件路径：
core/src/main/java/apoc/util/FileUtils.java
### 代码内容：
package apoc.util;

import apoc.ApocConfig;
import apoc.export.util.CountingInputStream;
import apoc.export.util.CountingReader;
import apoc.export.util.ExportConfig;
import apoc.util.hdfs.HDFSUtils;
import apoc.util.s3.S3URLConnection;
import apoc.util.s3.S3UploadUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.configuration.GraphDatabaseSettings;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static apoc.ApocConfig.APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM;
import static apoc.ApocConfig.apocConfig;
import static apoc.util.Util.ERROR_BYTES_OR_STRING;
import static apoc.util.Util.REDIRECT_LIMIT;
import static apoc.util.Util.readHttpInputStream;

/**
 * @author mh
 * @since 22.05.16
 */
public class FileUtils {

    public enum SupportedProtocols {
        http(true, null),
        https(true, null),
        ftp(true, null),
        s3(Util.classExists("com.amazonaws.services.s3.AmazonS3"),
                "apoc.util.s3.S3UrlStreamHandlerFactory"),
        gs(Util.classExists("com.google.cloud.storage.Storage"),
                "apoc.util.google.cloud.GCStorageURLStreamHandlerFactory"),
        hdfs(Util.classExists("org.apache.hadoop.fs.FileSystem"),
                "org.apache.hadoop.fs.FsUrlStreamHandlerFactory"),
        file(true, null);

        private final boolean enabled;

        private final String urlStreamHandlerClassName;

        SupportedProtocols(boolean enabled, String urlStreamHandlerClassName) {
            this.enabled = enabled;
            this.urlStreamHandlerClassName = urlStreamHandlerClassName;
        }

        public StreamConnection getStreamConnection(String urlAddress, Map<String, Object> headers, String payload) throws IOException {
            switch (this) {
                case s3:
                    return FileUtils.openS3InputStream(urlAddress);
                case hdfs:
                    return FileUtils.openHdfsInputStream(urlAddress);
                case ftp:
                case http:
                case https:
                case gs:
                    return readHttpInputStream(urlAddress, headers, payload, REDIRECT_LIMIT);
                default:
                    try {
                        return new StreamConnection.FileStreamConnection(URI.create(urlAddress));
                    } catch (IllegalArgumentException iae) {
                        try {
                            return new StreamConnection.FileStreamConnection(new URL(urlAddress).getFile());
                        } catch (MalformedURLException mue) {
                            if (mue.getMessage().contains("no protocol")) {
                                return new StreamConnection.FileStreamConnection(urlAddress);
                            }
                            throw mue;
                        }
                    }
            }
        }

        public OutputStream getOutputStream(String fileName, ExportConfig config) {
            if (fileName == null) return null;
            final CompressionAlgo compressionAlgo = CompressionAlgo.valueOf(config.getCompressionAlgo());
            final OutputStream outputStream;
            try {
                switch (this) {
                    case s3:
                        outputStream = S3UploadUtils.writeFile(fileName);
                        break;
                    case hdfs:
                        outputStream = HDFSUtils.writeFile(fileName);
                        break;
                    default:
                        final Path path = resolvePath(fileName);
                        outputStream = new FileOutputStream(path.toFile());
                }
                return new BufferedOutputStream(compressionAlgo.getOutputStream(outputStream));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public boolean isEnabled() {
            return enabled;
        }

        public URLStreamHandler createURLStreamHandler() {
            return Optional.ofNullable(urlStreamHandlerClassName)
                    .map(Util::createInstanceOrNull)
                    .map(urlStreamHandlerFactory -> ((URLStreamHandlerFactory) urlStreamHandlerFactory).createURLStreamHandler(this.name()))
                    .orElse(null);
        }

        public static SupportedProtocols from(String source) {
            try {
                final URL url = new URL(source);
                return from(url);
            } catch (MalformedURLException e) {
                if (!e.getMessage().contains("no protocol")) {
                    try {
                        // in case new URL(source) throw e.g. unknown protocol: hdfs, because of missing jar,
                        // we retrieve the related enum and throw the associated MissingDependencyException(..)
                        // otherwise we return unknown protocol: yyyyy
                        return SupportedProtocols.valueOf(new URI(source).getScheme());
                    } catch (Exception ignored) {}
                    throw new RuntimeException(e);
                }
                return SupportedProtocols.file;
            }
        }

        public static SupportedProtocols from(URL url) {
            return SupportedProtocols.of(url.getProtocol());
        }

        public static SupportedProtocols of(String name) {
            try {
                return SupportedProtocols.valueOf(name);
            } catch (Exception e) {
                return file;
            }
        }

    }

    public static final String ERROR_READ_FROM_FS_NOT_ALLOWED = "Import file %s not enabled, please set " + APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM + "=true in your neo4j.conf";
    public static final String ACCESS_OUTSIDE_DIR_ERROR = "You're providing a directory outside the import directory " +
            "defined into `dbms.directories.import`";

    public static CountingReader readerFor(Object input) throws IOException {
        return readerFor(input, null, null, CompressionAlgo.NONE.name());
    }

    public static CountingReader readerFor(Object input, String compressionAlgo) throws IOException {
        return readerFor(input, null, null, compressionAlgo);
    }

    public static CountingReader readerFor(Object input, Map<String, Object> headers, String payload, String compressionAlgo) throws IOException {
        return inputStreamFor(input, headers, payload, compressionAlgo).asReader();
    }

    public static CountingInputStream inputStreamFor(Object input, Map<String, Object> headers, String payload, String compressionAlgo) throws IOException {
        if (input == null) return null;
        if (input instanceof String) {
            String fileName = (String) input;
            apocConfig().checkReadAllowed(fileName);
            fileName = changeFileUrlIfImportDirectoryConstrained(fileName);
            return Util.openInputStream(fileName, headers, payload, compressionAlgo);
        } else if (input instanceof byte[]) {
            return getInputStreamFromBinary((byte[]) input, compressionAlgo);
        } else {
            throw new RuntimeException(ERROR_BYTES_OR_STRING);
        }
    }

    public static String changeFileUrlIfImportDirectoryConstrained(String url) throws IOException {
        if (isFile(url) && isImportUsingNeo4jConfig()) {
            if (!apocConfig().getBoolean(APOC_IMPORT_FILE_ALLOW__READ__FROM__FILESYSTEM)) {
                throw new RuntimeException(String.format(ERROR_READ_FROM_FS_NOT_ALLOWED, url));
            }
            final Path resolvedPath = resolvePath(url);
            return resolvedPath
                    .normalize()
                    .toUri()
                    .toString();
        }
        return url;
    }

    private static Path resolvePath(String url) throws IOException {
        Path urlPath = getPath(url);
        final Path resolvedPath;
        if (apocConfig().isImportFolderConfigured() && isImportUsingNeo4jConfig()) {
            Path basePath = Paths.get(apocConfig().getImportDir());
            urlPath = relativizeIfSamePrefix(urlPath, basePath);
            resolvedPath = basePath.resolve(urlPath).toAbsolutePath().normalize();
            if (!pathStartsWithOther(resolvedPath, basePath)) {
                throw new IOException(ACCESS_OUTSIDE_DIR_ERROR);
            }
        } else {
            resolvedPath = urlPath;
        }
        return resolvedPath;
    }

    private static Path relativizeIfSamePrefix(Path urlPath, Path basePath) {
        if (FilenameUtils.getPrefixLength(urlPath.toString()) > 0 && !urlPath.startsWith(basePath.toAbsolutePath())) {
            // if the import folder is configured to be used as root folder we consider
            // it as root directory in order to reproduce the same LOAD CSV behaviour
            urlPath = urlPath.getRoot().relativize(urlPath);
        }
        return urlPath;
    }

    private static Path getPath(String url) {
        Path urlPath;
        URL toURL = null;
        try {
            final URI uri = URI.create(url.trim());
            toURL = uri.toURL();
            urlPath = Paths.get(uri);
        } catch (Exception e) {
            if (toURL != null) {
                urlPath = Paths.get(StringUtils.isBlank(toURL.getFile()) ? toURL.getHost() : toURL.getFile());
            } else {
                urlPath = Paths.get(url);
            }
        }
        return urlPath;
    }

    private static boolean pathStartsWithOther(Path resolvedPath, Path basePath) throws IOException {
        try {
            return resolvedPath.toRealPath().startsWith(basePath.toRealPath());
        } catch (Exception e) {
            if (e instanceof NoSuchFileException) { // If we're about to creating a file this exception has been thrown
                return resolvedPath.normalize().startsWith(basePath);
            }
            return false;
        }
    }

    public static boolean isFile(String fileName) {
        return SupportedProtocols.from(fileName) == SupportedProtocols.file;
    }

    public static OutputStream getOutputStream(String fileName) {
        return getOutputStream(fileName, ExportConfig.EMPTY);
    }

    public static OutputStream getOutputStream(String fileName, ExportConfig config) {
        if (fileName.equals("-")) {
            return null;
        }
        return SupportedProtocols.from(fileName).getOutputStream(fileName, config);
    }

    public static boolean isImportUsingNeo4jConfig() {
        return apocConfig().getBoolean(ApocConfig.APOC_IMPORT_FILE_USE_NEO4J_CONFIG);
    }

    public static StreamConnection openS3InputStream(String urlAddress) throws IOException {
        if (!SupportedProtocols.s3.isEnabled()) {
            throw new MissingDependencyException("Cannot find the S3 jars in the plugins folder. \n" +
                    "Please put these files into the plugins folder :\n\n" +
                    "aws-java-sdk-core-x.y.z.jar\n" +
                    "aws-java-sdk-s3-x.y.z.jar\n" +
                    "httpclient-x.y.z.jar\n" +
                    "httpcore-x.y.z.jar\n" +
                    "joda-time-x.y.z.jar\n" +
                    "\nSee the documentation: https://neo4j-contrib.github.io/neo4j-apoc-procedures/#_loading_data_from_web_apis_json_xml_csv");
        }
        return S3URLConnection.openS3InputStream(new URL(urlAddress));
    }

    public static StreamConnection openHdfsInputStream(String urlAddress) throws IOException {
        if (!SupportedProtocols.hdfs.isEnabled()) {
            throw new MissingDependencyException("Cannot find the HDFS/Hadoop jars in the plugins folder. \n" +
                    "\nPlease, see the documentation: https://neo4j.com/labs/apoc/4.4/import/web-apis/");
        }
        return HDFSUtils.readFile(new URL(urlAddress));
    }

    /**
     * @return a File pointing to Neo4j's log directory, if it exists and is readable, null otherwise.
     */
    public static File getLogDirectory() {
        String neo4jHome = apocConfig().getString("dbms.directories.neo4j_home", "");
        String logDir = apocConfig().getString("dbms.directories.logs", "");

        File logs = logDir.isEmpty() ? new File(neo4jHome, "logs") : new File(logDir);

        if (logs.exists() && logs.canRead() && logs.isDirectory()) {
            return logs;
        }

        return null;
    }

    /**
     * @return a File representing the metrics directory that is listable and readable, or null if metrics don't exist,
     * aren't enabled, or aren't readable.
     */
    public static File getMetricsDirectory() {
        String neo4jHome = apocConfig().getString(GraphDatabaseSettings.neo4j_home.name());
        String metricsSetting = apocConfig().getString("dbms.directories.metrics", neo4jHome + File.separator + "metrics");

        File metricsDir = metricsSetting.isEmpty() ? new File(neo4jHome, "metrics") : new File(metricsSetting);

        if (metricsDir.exists() && metricsDir.canRead() && metricsDir.isDirectory() ) {
            return metricsDir;
        }

        return null;
    }

    // This is the list of dbms.directories.* valid configuration items for neo4j.
    // https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/
    // Usually these reside under the same root but because they're separately configurable, in the worst case
    // every one is on a different device.
    //
    // More likely, they'll be largely similar metrics.
    public static final List<String> NEO4J_DIRECTORY_CONFIGURATION_SETTING_NAMES = Arrays.asList(
//            "dbms.directories.certificates",  // not in 4.x version
            "dbms.directories.data",
            "dbms.directories.import",
            "dbms.directories.lib",
            "dbms.directories.logs",
//            "dbms.directories.metrics",  // metrics is only in EE
            "dbms.directories.plugins",
            "dbms.directories.run",
            "dbms.directories.tx_log",
            "dbms.directories.neo4j_home"
    );

    public static void closeReaderSafely(CountingReader reader) {
        if (reader != null) {
            try { reader.close(); } catch (IOException ignored) { }
        }
    }

    public static Path getPathFromUrlString(String urlDir) {
        return Paths.get(URI.create(urlDir));
    }

    public static CountingInputStream getInputStreamFromBinary(byte[] urlOrBinary, String compressionAlgo) {
        return CompressionAlgo.valueOf(compressionAlgo).toInputStream(urlOrBinary);
    }

    public static CountingReader getReaderFromBinary(byte[] urlOrBinary, String compressionAlgo) {
        try {
            return getInputStreamFromBinary(urlOrBinary, compressionAlgo).asReader();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Output:
【漏洞类型】路径遍历漏洞
【严重性】High
【漏洞摘要】此代码存在路径遍历漏洞，主要体现在`FileUtils.resolvePath`和`FileUtils.getPath`方法中。未验证的用户输入可能被用于构造文件路径，导致攻击者能够访问或写入系统中不应访问的文件。具体表现为，攻击者可以通过提供特定的文件路径来绕过目录限制，从而读取或写入任意文件。
【关键代码模式】
- `FileUtils.resolvePath`
- `FileUtils.getPath`
- `FileUtils.isImportUsingNeo4jConfig`
- `FileUtils.changeFileUrlIfImportDirectoryConstrained`
- `apocConfig.checkWriteAllowed`
- `apocConfig.checkReadAllowed`
【修复方法说明】修复方法通过规范化用户输入的文件路径，并使用`getCanonicalFile`来确保路径在允许的导入目录中。这样可以防止攻击者通过路径遍历漏洞绕过目录限制。适用的最佳实践包括对用户输入进行严格的路径验证和规范化处理。