Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/usr/local/www/pkg.php
### 代码内容：
<?php

##|+PRIV
##|*IDENT=page-package-settings
##|*NAME=Package: Settings
##|*DESCR=Allow access to the 'Package: Settings' page.
##|*MATCH=pkg.php*
##|-PRIV

require_once("guiconfig.inc");
require_once("pkg-utils.inc");

function domTT_title($title_msg) {
	print "onmouseout=\"this.style.color = ''; domTT_mouseout(this, event);\" onmouseover=\"domTT_activate(this, event, 'content', '".gettext($title_msg)."', 'trail', true, 'delay', 0, 'fade', 'both', 'fadeMax', 93, 'styleClass', 'niceTitle');\"";
}

$xml = $_REQUEST['xml'];

if ($xml == "") {
	$pgtitle = array(gettext("Package"), gettext("Editor"));
	$pglinks = array("", "@self");
	include("head.inc");
	print_info_box(gettext("No valid package defined."), 'danger', false);
	include("foot.inc");
	exit;
} else {
	$pkg_xml_prefix = "/usr/local/pkg/";
	$pkg_full_path = "{$pkg_xml_prefix}/{$xml}";
	$pkg_realpath = realpath($pkg_full_path);
	if (empty($pkg_realpath)) {
		$path_error = sprintf(gettext("Package path %s not found."), htmlspecialchars($pkg_full_path));
	} else if (substr_compare($pkg_realpath, $pkg_xml_prefix, 0, strlen($pkg_xml_prefix))) {
		$path_error = sprintf(gettext("Invalid path %s specified."), htmlspecialchars($pkg_full_path));
	}

	if (!empty($path_error)) {
		include("head.inc");
		print_info_box($path_error . "
" . gettext("Try reinstalling the package."), 'danger', false);
		include("foot.inc");
		die;
	}

	if (file_exists($pkg_full_path)) {
		$pkg = parse_xml_config_pkg($pkg_full_path, "packagegui");
	} else {
		include("head.inc");
		print_info_box(sprintf(gettext("File not found %s."), htmlspecialchars($xml)), 'danger', false);
		include("foot.inc");
		exit;
	}
}

if ($pkg['donotsave'] != "") {
	header("Location: pkg_edit.php?xml=" . $xml);
	exit;
}

if ($pkg['include_file'] != "") {
	require_once($pkg['include_file']);
}

if ($_REQUEST['startdisplayingat']) {
	$startdisplayingat = $_REQUEST['startdisplayingat'];
}

if ($_REQUEST['display_maximum_rows']) {
	if ($_REQUEST['display_maximum_rows']) {
		$display_maximum_rows = $_REQUEST['display_maximum_rows'];
	}
}

$evaledvar = $config['installedpackages'][xml_safe_fieldname($pkg['name'])]['config'];

if ($_REQUEST['act'] == "update") {

	if (is_array($config['installedpackages'][$pkg['name']]) && $pkg['name'] != "" && $_REQUEST['ids'] !="") {
		// get current values
		$current_values=$config['installedpackages'][$pkg['name']]['config'];
		// get updated ids
		parse_str($_REQUEST['ids'], $update_list);
		// sort ids to know what to change
		// useful to do not lose data when using sorting and paging
		$sort_list=$update_list['ids'];
		sort($sort_list);
		// apply updates
		foreach ($update_list['ids'] as $key=> $value) {
			$config['installedpackages'][$pkg['name']]['config'][$sort_list[$key]]=$current_values[$update_list['ids'][$key]];
		}
		// save current config
		write_config(gettext("Package configuration changes saved from package settings page."));
		// sync package
		eval ("{$pkg['custom_php_resync_config_command']}");
	}
	// function called via jquery, no need to continue after save changes.
	exit;
}
if ($_REQUEST['act'] == "del") {
	// loop through our fieldnames and automatically setup the fieldnames
	// in the environment.	ie: a fieldname of username with a value of
	// testuser would automatically eval $username = "testuser";
	foreach ($evaledvar as $ip) {
		if ($pkg['adddeleteeditpagefields']['columnitem']) {
			foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
				${xml_safe_fieldname($column['fielddescr'])} = $ip[xml_safe_fieldname($column['fieldname'])];
			}
		}
	}

	init_config_arr(array('installedpackages', xml_safe_fieldname($pkg['name']), 'config'));
	$a_pkg = &$config['installedpackages'][xml_safe_fieldname($pkg['name'])]['config'];

	if ($a_pkg[$_REQUEST['id']]) {
		unset($a_pkg[$_REQUEST['id']]);
		write_config(gettext("Package configuration item deleted from package settings page."));
		if ($pkg['custom_delete_php_command'] != "") {
			if ($pkg['custom_php_command_before_form'] != "") {
				eval($pkg['custom_php_command_before_form']);
			}
			eval($pkg['custom_delete_php_command']);
		}
		header("Location:  pkg.php?xml=" . $xml);
		exit;
	}
}

ob_start();

$iflist = get_configured_interface_with_descr(true);
$evaledvar = $config['installedpackages'][xml_safe_fieldname($pkg['name'])]['config'];

if ($pkg['custom_php_global_functions'] != "") {
	eval($pkg['custom_php_global_functions']);
}

if ($pkg['custom_php_command_before_form'] != "") {
	eval($pkg['custom_php_command_before_form']);
}

// Breadcrumb
if ($pkg['title'] != "") {
	/*if (!$only_edit) {						// Is any package still making use of this?? Is this something that is still wanted, considering the breadcrumb policy https://redmine.pfsense.org/issues/5527
 		$pkg['title'] = $pkg['title'] . '/Edit';		// If this needs to live on, then it has to be moved to run AFTER "foreach ($pkg['tabs']['tab'] as $tab)"-loop. This due to $pgtitle[] = $tab['text'];
	}*/
	if (strpos($pkg['title'], '/')) {
		$title = explode('/', $pkg['title']);

		foreach ($title as $subtitle) {
			$pgtitle[] = gettext($subtitle);
			$pglinks[] = "@self";
		}
	} else {
		$pgtitle = array(gettext("Package"), gettext($pkg['title']));
		$pglinks = array("", "@self");
	}
} else {
	$pgtitle = array(gettext("Package"), gettext("Editor"));
	$pglinks = array("", "@self");
}

if ($pkg['tabs'] != "") {
	$tab_array = array();
	foreach ($pkg['tabs']['tab'] as $tab) {
		if ($tab['tab_level']) {
			$tab_level = $tab['tab_level'];
		} else {
			$tab_level = 1;
		}
		if (isset($tab['active'])) {
			$active = true;
			$pgtitle[] = $tab['text'];
			$pglinks[] = "@self";
		} else {
			$active = false;
		}
		$urltmp = "";
		if ($tab['url'] != "") {
			$urltmp = $tab['url'];
		}
		if ($tab['xml'] != "") {
			$urltmp = "pkg_edit.php?xml=" . $tab['xml'];
		}

		$addresswithport = getenv("HTTP_HOST");
		$colonpos = strpos($addresswithport, ":");
		if ($colonpos !== False) {
			//my url is actually just the IP address of the pfsense box
			$myurl = substr($addresswithport, 0, $colonpos);
		} else {
			$myurl = $addresswithport;
		}
		// eval url so that above $myurl item can be processed if need be.
		$url = str_replace('$myurl', $myurl, $urltmp);

		$tab_array[$tab_level][] = array(
			$tab['text'],
			$active,
			$url
		);
	}

	ksort($tab_array);
}

if (!empty($pkg['tabs'])) {
	$shortcut_section = $pkg['shortcut_section'];
}

include("head.inc");
if (isset($tab_array)) {
	foreach ($tab_array as $tabid => $tab) {
		display_top_tabs($tab);
	}
}

?>

<?php
if ($_REQUEST['savemsg'] != "") {
	$savemsg = htmlspecialchars($_REQUEST['savemsg']);
}

if ($savemsg) {
	print_info_box($savemsg, 'success');
}
?>

<form action="pkg.php" name="pkgform" method="get">
	<input type='hidden' name='xml' value='<?=$_REQUEST['xml']?>' />

<?php
	/* Handle filtering bar A-Z */
	$include_filtering_inputbox = false;
	$colspan = 0;
	if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
		foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
			$colspan++;
		}
	}
	if ($pkg['fields']['field']) {
		// First find the sorting type field if it exists
		foreach ($pkg['fields']['field'] as $field) {
			if ($field['type'] == "sorting") {
				if (isset($field['include_filtering_inputbox'])) {
					$include_filtering_inputbox = true;
				}
				if ($display_maximum_rows < 1) {
					if ($field['display_maximum_rows']) {
						$display_maximum_rows = $field['display_maximum_rows'];
					}
				}
				echo "
";
				echo gettext("Filter by: ");
				$isfirst = true;
				for ($char = 65; $char < 91; $char++) {
					if (!$isfirst) {
						echo " | ";
					}
					echo "" . chr($char) . "";
					$isfirst = false;
				}
				echo "
";
				echo "
";
				if ($field['sortablefields']) {
					echo gettext("Filter field: ") . "<select name='pkg_filter_type'>";
					foreach ($field['sortablefields']['item'] as $si) {
						if ($si['name'] == $_REQUEST['pkg_filter_type']) {
							$SELECTED = "selected";
						} else {
							$SELECTED = "";
						}
						echo "<option value='{$si['name']}' {$SELECTED}>{$si['name']}</option>";
					}
					echo "</select>";
				}
				if ($include_filtering_inputbox) {
					echo '  ' . gettext("Filter text: ") . '<input id="pkg_filter" name="pkg_filter" value="' . htmlspecialchars($_REQUEST['pkg_filter']) . '" />';
					echo ' <button type="submit" value="Filter" class="btn btn-primary btn-xs">';
					echo '';
					echo gettext("Filter");
					echo "</button>";
				}
				echo "

";
			}
		}
	}
?>

<?php
	if ($display_maximum_rows) {
		$totalpages = ceil(round((count($evaledvar) / $display_maximum_rows), 9));
		$page = 1;
		$tmpcount = 0;
		$tmppp = 0;
		if (is_array($evaledvar)) {
			foreach ($evaledvar as $ipa) {
				if ($tmpcount == $display_maximum_rows) {
					$page++;
					$tmpcount = 0;
				}
				if ($tmppp == $startdisplayingat) {
					break;
				}
				$tmpcount++;
				$tmppp++;
			}
		}
		echo "
";
		echo "
";
		echo "
";
		echo "" . sprintf(gettext('Displaying page %1$s of %2$s'), $page, $totalpages) . "	";
		echo "" . gettext("Rows per page: ") . "<select onchange='document.pkgform.submit();' name='display_maximum_rows'>";
		for ($x = 0; $x < 250; $x++) {
			if ($x == $display_maximum_rows) {
				$SELECTED = "selected";
			} else {
				$SELECTED = "";
			}
			echo "<option value='$x' $SELECTED>$x</option>\n";
			$x = $x + 4;
		}
		echo "</select>
";
		echo "
";
		echo "
";
	}

	$cols = 0;
	if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
		foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
			echo "" . $column['fielddescr'] . "	";
			$cols++;
		}
	}
?>

<?php
	$i = 0;
	$pagination_counter = 0;
	if ($evaledvar && is_array($evaledvar)) {
		foreach ($evaledvar as $ip) {
			if ($startdisplayingat) {
				if ($i < $startdisplayingat) {
					$i++;
					continue;
				}
			}
			if ($_REQUEST['pkg_filter']) {
				// Handle filtered items
				if ($pkg['fields']['field'] && !$filter_regex) {
					// First find the sorting type field if it exists
					foreach ($pkg['fields']['field'] as $field) {
						if ($field['type'] == "sorting") {
							if ($field['sortablefields']['item']) {
								foreach ($field['sortablefields']['item'] as $sf) {
									if ($sf['name'] == $_REQUEST['pkg_filter_type']) {
										$filter_fieldname = $sf['fieldname'];
										#Use a default regex on sortable fields when none is declared
										if ($sf['regex']) {
											$filter_regex = str_replace("%FILTERTEXT%", $_REQUEST['pkg_filter'], trim($sf['regex']));
										} else {
											$filter_regex = "/{$_REQUEST['pkg_filter']}/i";
										}
									}
								}
							}
						}
					}
				}
				// Do we have something to filter on?
				unset($filter_matches);
				if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
					foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
						$fieldname = $ip[xml_safe_fieldname($column['fieldname'])];
						if ($column['fieldname'] == $filter_fieldname) {
							if ($filter_regex) {
								//echo "$filter_regex - $fieldname
";
								preg_match($filter_regex, $fieldname, $filter_matches);
								break;
							}
						}
					}
				}
				if (!$filter_matches) {
					$i++;
					continue;
				}
			}
			if ($pkg['adddeleteeditpagefields']['movable']) {
				echo "
\n";
			} else {
				echo "
\n";
			}
			if ($pkg['adddeleteeditpagefields']['columnitem'] != "") {
				foreach ($pkg['adddeleteeditpagefields']['columnitem'] as $column) {
					if ($column['fieldname'] == "description") {
						$class = "listbg";
					} else {
						$class = "listlr";
					}
?>
					<td class="<?=$class?>" ondblclick="document.location='pkg_edit.php?xml=<?=$xml?>&act=edit&id=<?=$i?>';">
<?php
					$fieldname = $ip[xml_safe_fieldname($column['fieldname'])];
					#Check if columnitem has a type field declared
					if ($column['type'] == "checkbox") {
						if ($fieldname == "") {
							echo gettext("No");
						} else {
							echo gettext("Yes");
						}
					} else if ($column['type'] == "interface") {
						echo $column['prefix'] . $iflist[$fieldname] . $column['suffix'];
					} else {
						$display_text = "";
						#Check if columnitem has an encoding field declared
						if ($column['encoding'] == "base64") {
							$display_text = $column['prefix'] . base64_decode($fieldname) . $column['suffix'];
						#Check if there is a custom info to show when $fieldname is not empty
						} else if ($column['listmodeon'] && $fieldname != "") {
							$display_text = $column['prefix'] . gettext($column['listmodeon']). $column['suffix'];
						#Check if there is a custom info to show when $fieldname is empty
						} else if ($column['listmodeoff'] && $fieldname == "") {
							$display_text = $column['prefix'] .gettext($column['listmodeoff']). $column['suffix'];
						} else {
							$display_text = $column['prefix'] . $fieldname ." ". $column['suffix'];
						}
						if (!isset($column['allow_html'])) {
							$display_text = htmlspecialchars($display_text);
						}
						echo $display_text;
					}
?>

<?php
				} // foreach columnitem
			} // if columnitem
?>

<?php
			#Show custom description to edit button if defined
			$edit_msg=($pkg['adddeleteeditpagefields']['edittext']?$pkg['adddeleteeditpagefields']['edittext']:gettext("Edit this item"));
?>
								<a class="fa fa-pencil" href="pkg_edit.php?xml=<?=$xml?>&act=edit&id=<?=$i?>" title="<?=$edit_msg?>">
<?php
			#Show custom description to delete button if defined
			$delete_msg=($pkg['adddeleteeditpagefields']['deletetext']?$pkg['adddeleteeditpagefields']['deletetext']:gettext("Delete this item"));
?>
								 <a class="fa fa-trash" href="pkg.php?xml=<?=$xml?>&act=del&id=<?=$i?>" title="<?=$delete_msg?>">

<?php
			echo "
\n"; // Pairs with an echo tr some way above
			// Handle pagination and display_maximum_rows
			if ($display_maximum_rows) {
				if ($pagination_counter == ($display_maximum_rows-1) or
					$i == (count($evaledvar)-1)) {
					$colcount = count($pkg['adddeleteeditpagefields']['columnitem']);
					$final_footer = "";
					$final_footer .= "
";
					$final_footer .= "

";
					$final_footer .= "";
					$startingat = $startdisplayingat - $display_maximum_rows;
					if ($startingat > -1) {
						$final_footer .= "";
					} else if ($startdisplayingat > 1) {
						$final_footer .= "";
					}
					$final_footer .= "<< " . gettext("Previous page") . "";
					if ($tmppp + $display_maximum_rows > count($evaledvar)) {
						$endingrecord = count($evaledvar);
					} else {
						$endingrecord = $tmppp + $display_maximum_rows;
					}
					$final_footer .= "	";
					$tmppp++;
					$final_footer .= "Displaying {$tmppp} - {$endingrecord} / " . count($evaledvar) . " records";
					$final_footer .= "	 ";
					if (($i+1) < count($evaledvar)) {
						$final_footer .= "";
					}
					$final_footer .= "" . gettext("Next page") . " >>";
					$final_footer .= "

";
					$i = count($evaledvar);
					break;
				}
			}
			$i++;
			$pagination_counter++;
		} // foreach evaledvar
	} // if evaledvar
?>

					<td colspan="<?=$cols?>">

<?php
	#Show custom description to add button if defined
	$add_msg=($pkg['adddeleteeditpagefields']['addtext']?$pkg['adddeleteeditpagefields']['addtext']:gettext("Add a new item"));
?>
								<a href="pkg_edit.php?xml=<?=$xml?>&id=<?=$i?>" class="btn btn-sm btn-success" title="<?=$add_msg?>"><?=gettext('Add')?>
<?php
	#Show description button and info if defined
	if ($pkg['adddeleteeditpagefields']['description']) {
?>

									<?=$pkg['adddeleteeditpagefields']['description']?>

<?php
	}
?>

				<?=$final_footer?>

		<button class="btn btn-primary" type="button" value="Save" name="Submit" onclick="save_changes_to_xml('<?=$xml?>')"><?=gettext("Save")?></button>

</form>
<?php
echo "";
echo "";

include("foot.inc"); ?>

Output:
【漏洞类型】路径遍历漏洞
【严重性】High
【漏洞摘要】此代码存在路径遍历漏洞，允许攻击者通过构造恶意的`xml`参数来访问或执行任意文件。具体表现为在第43行，`$pkg_full_path`直接使用了未验证的用户输入`$_REQUEST['xml']`，这可能导致攻击者遍历文件系统或执行任意代码。

【关键代码模式】
- `$_REQUEST['xml']`：未验证的用户输入。
- `realpath($pkg_full_path)`：用于解析文件路径。
- `file_exists($pkg_full_path)`：检查文件是否存在。
- `parse_xml_config_pkg($pkg_full_path, "packagegui")`：解析XML配置文件。
- `eval()`：用于执行动态生成的PHP代码。

【修复方法说明】
修复方法通过`htmlspecialchars(strip_tags($_REQUEST['pkg_filter']))`清理用户输入的`pkg_filter`参数，防止恶意正则表达式影响系统。同时，移除了不必要的`echo`语句，以减少信息泄露的风险。


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

import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.policy.PasswordPolicyNotMetException;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.UserPermissionEvaluator;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.utils.SearchQueryUtils;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import static org.keycloak.userprofile.UserProfileContext.USER_API;

/**
 * Base resource for managing users
 *
 * @resource Users
 * @author Bill Burke
 * @version $Revision: 1 $
 */
public class UsersResource {

    private static final Logger logger = Logger.getLogger(UsersResource.class);
    private static final String SEARCH_ID_PARAMETER = "id:";

    protected RealmModel realm;

    private AdminPermissionEvaluator auth;

    private AdminEventBuilder adminEvent;

    @Context
    protected ClientConnection clientConnection;

    @Context
    protected KeycloakSession session;

    @Context
    protected HttpHeaders headers;

    public UsersResource(RealmModel realm, AdminPermissionEvaluator auth, AdminEventBuilder adminEvent) {
        this.auth = auth;
        this.realm = realm;
        this.adminEvent = adminEvent.resource(ResourceType.USER);
    }

    /**
     * Create a new user
     *
     * Username must be unique.
     *
     * @param rep
     * @return
     */
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createUser(final UserRepresentation rep) {
        // first check if user has manage rights
        try {
            auth.users().requireManage();
        }
        catch (ForbiddenException exception) {
            // if user does not have manage rights, fallback to fine grain admin permissions per group
            if (rep.getGroups() != null) {
                // if groups is part of the user rep, check if admin has manage_members and manage_membership on each group
                for (String groupPath : rep.getGroups()) {
                    GroupModel group = KeycloakModelUtils.findGroupByPath(realm, groupPath);
                    if (group != null) {
                        auth.groups().requireManageMembers(group);
                        auth.groups().requireManageMembership(group);
                    } else {
                        return ErrorResponse.error(String.format("Group %s not found", groupPath), Response.Status.BAD_REQUEST);
                    }
                }
            } else {
                // propagate exception if no group specified
                throw exception;
            }
        }

        String username = rep.getUsername();
        if(realm.isRegistrationEmailAsUsername()) {
            username = rep.getEmail();
        }
        if (ObjectUtil.isBlank(username)) {
            return ErrorResponse.error("User name is missing", Response.Status.BAD_REQUEST);
        }

        // Double-check duplicated username and email here due to federation
        if (session.users().getUserByUsername(realm, username) != null) {
            return ErrorResponse.exists("User exists with same username");
        }
        if (rep.getEmail() != null && !realm.isDuplicateEmailsAllowed()) {
            try {
                if(session.users().getUserByEmail(realm, rep.getEmail()) != null) {
                    return ErrorResponse.exists("User exists with same email");
                }
            } catch (ModelDuplicateException e) {
                return ErrorResponse.exists("User exists with same email");
            }
        }

        UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);

        UserProfile profile = profileProvider.create(USER_API, rep.toAttributes());

        try {
            Response response = UserResource.validateUserProfile(profile, null, session);
            if (response != null) {
                return response;
            }

            UserModel user = profile.create();

            UserResource.updateUserFromRep(profile, user, rep, session, false);
            RepresentationToModel.createFederatedIdentities(rep, session, realm, user);
            RepresentationToModel.createGroups(rep, realm, user);

            RepresentationToModel.createCredentials(rep, session, realm, user, true);
            adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri(), user.getId()).representation(rep).success();

            if (session.getTransactionManager().isActive()) {
                session.getTransactionManager().commit();
            }

            return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(user.getId()).build()).build();
        } catch (ModelDuplicateException e) {
            if (session.getTransactionManager().isActive()) {
                session.getTransactionManager().setRollbackOnly();
            }
            return ErrorResponse.exists("User exists with same username or email");
        } catch (PasswordPolicyNotMetException e) {
            if (session.getTransactionManager().isActive()) {
                session.getTransactionManager().setRollbackOnly();
            }
            return ErrorResponse.error("Password policy not met", Response.Status.BAD_REQUEST);
        } catch (ModelException me){
            if (session.getTransactionManager().isActive()) {
                session.getTransactionManager().setRollbackOnly();
            }
            logger.warn("Could not create user", me);
            return ErrorResponse.error("Could not create user", Response.Status.BAD_REQUEST);
        }
    }
    /**
     * Get representation of the user
     *
     * @param id User id
     * @return
     */
    @Path("{id}")
    public UserResource user(final @PathParam("id") String id) {
        UserModel user = session.users().getUserById(realm, id);
        if (user == null) {
            // we do this to make sure somebody can't phish ids
            if (auth.users().canQuery()) throw new NotFoundException("User not found");
            else throw new ForbiddenException();
        }
        UserResource resource = new UserResource(realm, user, auth, adminEvent);
        ResteasyProviderFactory.getInstance().injectProperties(resource);
        //resourceContext.initResource(users);
        return resource;
    }

    /**
     * Get users
     *
     * Returns a stream of users, filtered according to query parameters.
     *
     * @param search A String contained in username, first or last name, or email
     * @param last A String contained in lastName, or the complete lastName, if param "exact" is true
     * @param first A String contained in firstName, or the complete firstName, if param "exact" is true
     * @param email A String contained in email, or the complete email, if param "exact" is true
     * @param username A String contained in username, or the complete username, if param "exact" is true
     * @param emailVerified whether the email has been verified
     * @param idpAlias The alias of an Identity Provider linked to the user
     * @param idpUserId The userId at an Identity Provider linked to the user
     * @param firstResult Pagination offset
     * @param maxResults Maximum results size (defaults to 100)
     * @param enabled Boolean representing if user is enabled or not
     * @param briefRepresentation Boolean which defines whether brief representations are returned (default: false)
     * @param exact Boolean which defines whether the params "last", "first", "email" and "username" must match exactly
     * @param searchQuery A query to search for custom attributes, in the format 'key1:value2 key2:value2'
     * @return a non-null {@code Stream} of users
     */
    @GET
    @NoCache
    @Produces(MediaType.APPLICATION_JSON)
    public Stream<UserRepresentation> getUsers(@QueryParam("search") String search,
                                               @QueryParam("lastName") String last,
                                               @QueryParam("firstName") String first,
                                               @QueryParam("email") String email,
                                               @QueryParam("username") String username,
                                               @QueryParam("emailVerified") Boolean emailVerified,
                                               @QueryParam("idpAlias") String idpAlias,
                                               @QueryParam("idpUserId") String idpUserId,
                                               @QueryParam("first") Integer firstResult,
                                               @QueryParam("max") Integer maxResults,
                                               @QueryParam("enabled") Boolean enabled,
                                               @QueryParam("briefRepresentation") Boolean briefRepresentation,
                                               @QueryParam("exact") Boolean exact,
                                               @QueryParam("q") String searchQuery) {
        UserPermissionEvaluator userPermissionEvaluator = auth.users();

        userPermissionEvaluator.requireQuery();

        firstResult = firstResult != null ? firstResult : -1;
        maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;

        Map<String, String> searchAttributes = searchQuery == null
                ? Collections.emptyMap()
                : SearchQueryUtils.getFields(searchQuery);

        Stream<UserModel> userModels = Stream.empty();
        if (search != null) {
            if (search.startsWith(SEARCH_ID_PARAMETER)) {
                UserModel userModel =
                        session.users().getUserById(realm, search.substring(SEARCH_ID_PARAMETER.length()).trim());
                if (userModel != null) {
                    userModels = Stream.of(userModel);
                }
            } else {
                Map<String, String> attributes = new HashMap<>();
                attributes.put(UserModel.SEARCH, search.trim());
                if (enabled != null) {
                    attributes.put(UserModel.ENABLED, enabled.toString());
                }
                return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult,
                        maxResults, false);
            }
        } else if (last != null || first != null || email != null || username != null || emailVerified != null
                || idpAlias != null || idpUserId != null || enabled != null || exact != null || !searchAttributes.isEmpty()) {
                    Map<String, String> attributes = new HashMap<>();
                    if (last != null) {
                        attributes.put(UserModel.LAST_NAME, last);
                    }
                    if (first != null) {
                        attributes.put(UserModel.FIRST_NAME, first);
                    }
                    if (email != null) {
                        attributes.put(UserModel.EMAIL, email);
                    }
                    if (username != null) {
                        attributes.put(UserModel.USERNAME, username);
                    }
                    if (emailVerified != null) {
                        attributes.put(UserModel.EMAIL_VERIFIED, emailVerified.toString());
                    }
                    if (idpAlias != null) {
                        attributes.put(UserModel.IDP_ALIAS, idpAlias);
                    }
                    if (idpUserId != null) {
                        attributes.put(UserModel.IDP_USER_ID, idpUserId);
                    }
                    if (enabled != null) {
                        attributes.put(UserModel.ENABLED, enabled.toString());
                    }
                    if (exact != null) {
                        attributes.put(UserModel.EXACT, exact.toString());
                    }

                    attributes.putAll(searchAttributes);

                    return searchForUser(attributes, realm, userPermissionEvaluator, briefRepresentation, firstResult,
                            maxResults, true);
                } else {
                    return searchForUser(new HashMap<>(), realm, userPermissionEvaluator, briefRepresentation,
                            firstResult, maxResults, false);
                }

        return toRepresentation(realm, userPermissionEvaluator, briefRepresentation, userModels);
    }

    /**
     * Returns the number of users that match the given criteria.
     * It can be called in three different ways.
     * 1. Don't specify any criteria and pass {@code null}. The number of all
     * users within that realm will be returned.
     *

     * 2. If {@code search} is specified other criteria such as {@code last} will
     * be ignored even though you set them. The {@code search} string will be
     * matched against the first and last name, the username and the email of a
     * user.
     *

     * 3. If {@code search} is unspecified but any of {@code last}, {@code first},
     * {@code email} or {@code username} those criteria are matched against their
     * respective fields on a user entity. Combined with a logical and.
     *
     * @param search   arbitrary search string for all the fields below
     * @param last     last name filter
     * @param first    first name filter
     * @param email    email filter
     * @param username username filter
     * @return the number of users that match the given criteria
     */
    @Path("count")
    @GET
    @NoCache
    @Produces(MediaType.APPLICATION_JSON)
    public Integer getUsersCount(@QueryParam("search") String search,
                                 @QueryParam("lastName") String last,
                                 @QueryParam("firstName") String first,
                                 @QueryParam("email") String email,
                                 @QueryParam("emailVerified") Boolean emailVerified,
                                 @QueryParam("username") String username) {
        UserPermissionEvaluator userPermissionEvaluator = auth.users();
        userPermissionEvaluator.requireQuery();

        if (search != null) {
            if (search.startsWith(SEARCH_ID_PARAMETER)) {
                UserModel userModel = session.users().getUserById(realm, search.substring(SEARCH_ID_PARAMETER.length()).trim());
                return userModel != null && userPermissionEvaluator.canView(userModel) ? 1 : 0;
            } else if (userPermissionEvaluator.canView()) {
                return session.users().getUsersCount(realm, search.trim());
            } else {
                return session.users().getUsersCount(realm, search.trim(), auth.groups().getGroupsWithViewPermission());
            }
        } else if (last != null || first != null || email != null || username != null || emailVerified != null) {
            Map<String, String> parameters = new HashMap<>();
            if (last != null) {
                parameters.put(UserModel.LAST_NAME, last);
            }
            if (first != null) {
                parameters.put(UserModel.FIRST_NAME, first);
            }
            if (email != null) {
                parameters.put(UserModel.EMAIL, email);
            }
            if (username != null) {
                parameters.put(UserModel.USERNAME, username);
            }
            if (emailVerified != null) {
                parameters.put(UserModel.EMAIL_VERIFIED, emailVerified.toString());
            }
            if (userPermissionEvaluator.canView()) {
                return session.users().getUsersCount(realm, parameters);
            } else {
                return session.users().getUsersCount(realm, parameters, auth.groups().getGroupsWithViewPermission());
            }
        } else if (userPermissionEvaluator.canView()) {
            return session.users().getUsersCount(realm);
        } else {
            return session.users().getUsersCount(realm, auth.groups().getGroupsWithViewPermission());
        }
    }

    /**
     * Get representation of the user
     *
     * @param id User id
     * @return
     */
    @Path("profile")
    public UserProfileResource userProfile() {
        UserProfileResource resource = new UserProfileResource(realm, auth);
        ResteasyProviderFactory.getInstance().injectProperties(resource);
        return resource;
    }

    private Stream<UserRepresentation> searchForUser(Map<String, String> attributes, RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Integer firstResult, Integer maxResults, Boolean includeServiceAccounts) {
        session.setAttribute(UserModel.INCLUDE_SERVICE_ACCOUNT, includeServiceAccounts);

        if (!auth.users().canView()) {
            Set<String> groupModels = auth.groups().getGroupsWithViewPermission();

            if (!groupModels.isEmpty()) {
                session.setAttribute(UserModel.GROUPS, groupModels);
            }
        }

        Stream<UserModel> userModels = session.users().searchForUserStream(realm, attributes, firstResult, maxResults);
        return toRepresentation(realm, usersEvaluator, briefRepresentation, userModels);
    }

    private Stream<UserRepresentation> toRepresentation(RealmModel realm, UserPermissionEvaluator usersEvaluator, Boolean briefRepresentation, Stream<UserModel> userModels) {
        boolean briefRepresentationB = briefRepresentation != null && briefRepresentation;
        boolean canViewGlobal = usersEvaluator.canView();

        usersEvaluator.grantIfNoPermission(session.getAttribute(UserModel.GROUPS) != null);
        return userModels.filter(user -> canViewGlobal || usersEvaluator.canView(user))
                .map(user -> {
                    UserRepresentation userRep = briefRepresentationB
                            ? ModelToRepresentation.toBriefRepresentation(user)
                            : ModelToRepresentation.toRepresentation(session, realm, user);
                    userRep.setAccess(usersEvaluator.getAccess(user));
                    return userRep;
                });
    }
}

Output:
【漏洞类型】路径遍历漏洞
【严重性】Medium
【漏洞摘要】在原始代码的`createUser`方法中，当用户没有全局管理权限时，会检查每个组的`manage_members`和`manage_membership`权限。然而，这个检查是在一个`try-catch`块中进行的，如果用户没有全局管理权限但指定了组，则会抛出`ForbiddenException`，并且不会记录任何日志或返回任何错误信息。这可能导致攻击者通过猜测或尝试不同的组路径来遍历服务器上的组，从而获取未授权的信息。
【关键代码模式】`auth.groups().requireManageMembers(group);`、`auth.groups().requireManageMembership(group);`、`try-catch`块。
【修复方法说明】修复方法通过添加一个新的辅助方法`canCreateGroupMembers`来检查是否启用了细粒度权限管理功能，并在该方法中进行组权限验证。如果细粒度权限管理功能未启用或组权限验证失败，则不会抛出`ForbiddenException`，而是返回适当的错误信息。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
android-gif-drawable/src/main/c/decoding.c
### 代码内容：
#include "gif.h"

static bool updateGCB(GifInfo *info, uint_fast32_t *lastAllocatedGCBIndex) {
	if (*lastAllocatedGCBIndex < info->gifFilePtr->ImageCount) {
		GraphicsControlBlock *tmpInfos = reallocarray(info->controlBlock, info->gifFilePtr->ImageCount + 1, sizeof(GraphicsControlBlock));
		if (tmpInfos == NULL) {
			info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
			return false;
		}
		*lastAllocatedGCBIndex = info->gifFilePtr->ImageCount;
		info->controlBlock = tmpInfos;
		setGCBDefaults(&info->controlBlock[info->gifFilePtr->ImageCount]);
	}
	return true;
}

void DDGifSlurp(GifInfo *info, bool decode, bool exitAfterFrame) {
	GifRecordType RecordType;
	GifByteType *ExtData;
	int ExtFunction;
	GifFileType *gifFilePtr;
	gifFilePtr = info->gifFilePtr;
	uint_fast32_t lastAllocatedGCBIndex = 0;
	do {
		if (DGifGetRecordType(gifFilePtr, &RecordType) == GIF_ERROR && gifFilePtr->Error != D_GIF_ERR_WRONG_RECORD) {
			break;
		}
		bool isInitialPass = !decode && !exitAfterFrame;
		switch (RecordType) {
			case IMAGE_DESC_RECORD_TYPE:

				if (DGifGetImageDesc(gifFilePtr, isInitialPass) == GIF_ERROR) {
					break;
				}

				if (isInitialPass) {
					if (!updateGCB(info, &lastAllocatedGCBIndex)) {
						break;
					}
				}

				if (decode) {
					const uint_fast32_t newRasterSize = gifFilePtr->Image.Width * gifFilePtr->Image.Height;
					if (newRasterSize == 0) {
						free(info->rasterBits);
						info->rasterBits = NULL;
						info->rasterSize = newRasterSize;
						return;
					}
					const int_fast32_t widthOverflow = gifFilePtr->Image.Width - info->originalWidth;
					const int_fast32_t heightOverflow = gifFilePtr->Image.Height - info->originalHeight;
					if (newRasterSize > info->rasterSize || widthOverflow > 0 || heightOverflow > 0) {
						void *tmpRasterBits = reallocarray(info->rasterBits, newRasterSize, sizeof(GifPixelType));
						if (tmpRasterBits == NULL) {
							gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
							break;
						}
						info->rasterBits = tmpRasterBits;
						info->rasterSize = newRasterSize;
					}
					if (gifFilePtr->Image.Interlace) {
						uint_fast16_t i, j;
						/*
						 * The way an interlaced image should be read -
						 * offsets and jumps...
						 */
						uint_fast8_t InterlacedOffset[] = {0, 4, 2, 1};
						uint_fast8_t InterlacedJumps[] = {8, 8, 4, 2};
						/* Need to perform 4 passes on the image */
						for (i = 0; i < 4; i++)
							for (j = InterlacedOffset[i]; j < gifFilePtr->Image.Height; j += InterlacedJumps[i]) {
								if (DGifGetLine(gifFilePtr, info->rasterBits + j * gifFilePtr->Image.Width, gifFilePtr->Image.Width) == GIF_ERROR)
									break;
							}
					} else {
						if (DGifGetLine(gifFilePtr, info->rasterBits, gifFilePtr->Image.Width * gifFilePtr->Image.Height) == GIF_ERROR) {
							break;
						}
					}

					if (info->sampleSize > 1) {
						unsigned char *dst = info->rasterBits;
						unsigned char *src = info->rasterBits;
						unsigned char *const srcEndImage = info->rasterBits + gifFilePtr->Image.Width * gifFilePtr->Image.Height;
						do {
							unsigned char *srcNextLineStart = src + gifFilePtr->Image.Width * info->sampleSize;
							unsigned char *const srcEndLine = src + gifFilePtr->Image.Width;
							unsigned char *dstEndLine = dst + gifFilePtr->Image.Width / info->sampleSize;
							do {
								*dst = *src;
								dst++;
								src += info->sampleSize;
							} while (src < srcEndLine);
							dst = dstEndLine;
							src = srcNextLineStart;
						} while (src < srcEndImage);
					}
					return;
				} else {
					do {
						if (DGifGetCodeNext(gifFilePtr, &ExtData) == GIF_ERROR) {
							break;
						}
					} while (ExtData != NULL);
					if (exitAfterFrame) {
						return;
					}
				}
				break;

			case EXTENSION_RECORD_TYPE:
				if (DGifGetExtension(gifFilePtr, &ExtFunction, &ExtData) == GIF_ERROR) {
					break;
				}
				if (isInitialPass) {
					updateGCB(info, &lastAllocatedGCBIndex);
					if (readExtensions(ExtFunction, ExtData, info) == GIF_ERROR) {
						break;
					}
				}
				while (ExtData != NULL) {
					if (DGifGetExtensionNext(gifFilePtr, &ExtData) == GIF_ERROR) {
						break;
					}
					if (isInitialPass && readExtensions(ExtFunction, ExtData, info) == GIF_ERROR) {
						break;
					}
				}
				break;

			case TERMINATE_RECORD_TYPE:
				break;

			default:
				break;
		}
	} while (RecordType != TERMINATE_RECORD_TYPE);

	info->rewindFunction(info);
}

static int readExtensions(int ExtFunction, GifByteType *ExtData, GifInfo *info) {
	if (ExtData == NULL) {
		return GIF_OK;
	}
	if (ExtFunction == GRAPHICS_EXT_FUNC_CODE) {
		GraphicsControlBlock *GCB = &info->controlBlock[info->gifFilePtr->ImageCount];
		if (DGifExtensionToGCB(ExtData[0], ExtData + 1, GCB) == GIF_ERROR) {
			return GIF_ERROR;
		}

		GCB->DelayTime = GCB->DelayTime > 1 ? GCB->DelayTime * 10 : DEFAULT_FRAME_DURATION_MS;
	}
	else if (ExtFunction == COMMENT_EXT_FUNC_CODE) {
		if (getComment(ExtData, info) == GIF_ERROR) {
			info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
			return GIF_ERROR;
		}
	}
	else if (ExtFunction == APPLICATION_EXT_FUNC_CODE) {
		char const *string = (char const *) (ExtData + 1);
		if (strncmp("NETSCAPE2.0", string, ExtData[0]) == 0
		    || strncmp("ANIMEXTS1.0", string, ExtData[0]) == 0) {
			if (DGifGetExtensionNext(info->gifFilePtr, &ExtData) == GIF_ERROR) {
				return GIF_ERROR;
			}
			if (ExtData && ExtData[0] == 3 && ExtData[1] == 1) {
				uint_fast16_t loopCount = (uint_fast16_t) (ExtData[2] + (ExtData[3] << 8));
				if (loopCount) {
					loopCount++;
				}
				info->loopCount = loopCount;
			}
		}
	}
	return GIF_OK;
}

static int getComment(GifByteType *Bytes, GifInfo *info) {
	unsigned int len = (unsigned int) Bytes[0];
	size_t offset = info->comment != NULL ? strlen(info->comment) : 0;
	char *ret = reallocarray(info->comment, len + offset + 1, sizeof(char));
	if (ret != NULL) {
		memcpy(ret + offset, &Bytes[1], len);
		ret[len + offset] = 0;
		info->comment = ret;
		return GIF_OK;
	}
	info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
	return GIF_ERROR;
}

Output:
【漏洞类型】缓冲区溢出
【严重性】High
【漏洞摘要】原始代码中存在的漏洞是缓冲区溢出，特别是在处理GIF文件中的注释扩展时。具体表现为`getComment`函数中未对注释的长度进行限制，可能导致分配的内存不足，从而引发`reallocarray`失败，返回`NULL`。当`Bytes[0]`的值非常大时，`reallocarray`可能会尝试分配过多的内存，导致内存不足或系统崩溃。
【关键代码模式】`getComment`函数中对`Bytes[0]`的使用，以及`reallocarray`函数的调用。
【修复方法说明】修复方法通过定义`COMMENT_LENGTH_MAX`宏来限制注释的最大长度，从而避免了分配过多内存的风险。


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

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import edu.stanford.nlp.io.IOUtils;
import edu.stanford.nlp.util.logging.Redwood;

/**
 * Provides some utilities for dealing with XML files, both by properly
 * parsing them and by using the methods of a desperate Perl hacker.
 *
 * @author Teg Grenager
 * @author Grace Muzny
 */
public class XMLUtils  {

  /** A logger for this class */
  private static final Redwood.RedwoodChannels log = Redwood.channels(XMLUtils.class);

  private XMLUtils() {} // only static methods

  public static DocumentBuilderFactory safeDocumentBuilderFactory() {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
      dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
      dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
      dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
      dbf.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", false);
      dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
    } catch (ParserConfigurationException e) {
      log.warn(e);
    }
    return dbf;
  }

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   *
   * @return List of String text contents of tags.
   */
  public static List<String> getTextContentFromTagsFromFile(File f, String tag) {
    List<String> sents = Generics.newArrayList();
    try {
      sents = getTextContentFromTagsFromFileSAXException(f, tag);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   * If the text contents contains embedded tags, strips the embedded tags out
   * of the returned text. E.g., {@code <s>This is a <s>sentence</s> with embedded tags
   * </s>} would return the list containing ["This is a sentence with embedded
   * tags", "sentence"].
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of String text contents of tags.
   */
  private static List<String> getTextContentFromTagsFromFileSAXException(
          File f, String tag) throws SAXException {
    List<String> sents = Generics.newArrayList();
    try {
      DocumentBuilderFactory dbf = safeDocumentBuilderFactory();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(f);
      doc.getDocumentElement().normalize();

      NodeList nodeList=doc.getElementsByTagName(tag);
      for (int i = 0; i < nodeList.getLength(); i++) {
        // Get element
        Element element = (Element)nodeList.item(i);
        String raw = element.getTextContent();
        StringBuilder builtUp = new StringBuilder();
        boolean inTag = false;
        for (int j = 0; j < raw.length(); j++) {
          if (raw.charAt(j) == '<') {
            inTag = true;
          }
          if (!inTag) {
            builtUp.append(raw.charAt(j));
          }
          if (raw.charAt(j) == '>') {
            inTag = false;
          }
        }
        sents.add(builtUp.toString());
      }
    } catch (IOException | ParserConfigurationException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   *
   * @return List of String text contents of tags.
   */
  public static List<Element> getTagElementsFromFile(File f, String tag) {
    List<Element> sents = Generics.newArrayList();
    try {
      sents = getTagElementsFromFileSAXException(f, tag);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the text content of all nodes in the given file with the given tag.
   * If the text contents contains embedded tags, strips the embedded tags out
   * of the returned text. E.g., {@code <s>This is a <s>sentence</s> with embedded tags
   * </s>} would return the list containing ["This is a sentence with embedded
   * tags", "sentence"].
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of String text contents of tags.
   */
  private static List<Element> getTagElementsFromFileSAXException(
          File f, String tag) throws SAXException {
    List<Element> sents = Generics.newArrayList();
    try {
      DocumentBuilderFactory dbf = safeDocumentBuilderFactory();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(f);
      doc.getDocumentElement().normalize();

      NodeList nodeList=doc.getElementsByTagName(tag);
      for (int i = 0; i < nodeList.getLength(); i++) {
        // Get element
        Element element = (Element)nodeList.item(i);
        sents.add(element);
      }
    } catch (IOException | ParserConfigurationException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the two previous siblings and two next siblings.
   *
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFile(File f, String tag) {
    List<Triple<String, Element, String>> sents = Generics.newArrayList();
    try {
      sents = getTagElementTriplesFromFileSAXException(f, tag);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the previous and next siblings up to max numIncludedSiblings.
   *
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFileNumBounded(File f,
                                                                                String tag,
                                                                                int num) {
    List<Triple<String, Element, String>> sents = Generics.newArrayList();
    try {
      sents = getTagElementTriplesFromFileNumBoundedSAXException(f, tag, num);
    } catch (SAXException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the two previous siblings and two next siblings.
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFileSAXException(
      File f, String tag) throws SAXException {
    return  getTagElementTriplesFromFileNumBoundedSAXException(f, tag, 2);
  }

  /**
   * Returns the elements in the given file with the given tag associated with
   * the text content of the previous and next siblings up to max numIncludedSiblings.
   *
   * @throws SAXException if tag doesn't exist in the file.
   * @return List of {@code Triple<String, Element, String>} Targeted elements surrounded
   * by the text content of the two previous siblings and two next siblings.
   */
  public static List<Triple<String, Element, String>> getTagElementTriplesFromFileNumBoundedSAXException(
      File f, String tag, int numIncludedSiblings) throws SAXException {
    List<Triple<String, Element, String>> sents = Generics.newArrayList();
    try {
      DocumentBuilderFactory dbf = safeDocumentBuilderFactory();
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(f);
      doc.getDocumentElement().normalize();

      NodeList nodeList=doc.getElementsByTagName(tag);
      for (int i = 0; i < nodeList.getLength(); i++) {
        // Get element
        Node prevNode = nodeList.item(i).getPreviousSibling();
        String prev = "";
        int count = 0;
        while (prevNode != null && count <= numIncludedSiblings) {
          prev = prevNode.getTextContent() + prev;
          prevNode = prevNode.getPreviousSibling();
          count++;
        }

        Node nextNode = nodeList.item(i).getNextSibling();
        String next = "";
        count = 0;
        while (nextNode != null && count <= numIncludedSiblings) {
          next = next + nextNode.getTextContent();
          nextNode = nextNode.getNextSibling();
          count++;
        }
        Element element = (Element)nodeList.item(i);
        Triple<String, Element, String> t = new Triple<>(prev, element, next);
        sents.add(t);
      }
    } catch (IOException | ParserConfigurationException e) {
      log.warn(e);
    }
    return sents;
  }

  /**
   * Returns a non-validating XML parser. The parser ignores both DTDs and XSDs.
   *
   * @return An XML parser in the form of a DocumentBuilder
   */
  public static DocumentBuilder getXmlParser() {
    DocumentBuilder db = null;
    try {
      DocumentBuilderFactory dbf = safeDocumentBuilderFactory();
      dbf.setValidating(false);

      //Disable DTD loading and validation
      //See http://stackoverflow.com/questions/155101/make-documentbuilder-parse-ignore-dtd-references
      dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
      dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

      db = dbf.newDocumentBuilder();
      db.setErrorHandler(new SAXErrorHandler());

    } catch (ParserConfigurationException e) {
      log.warnf("%s: Unable to create XML parser\n", XMLUtils.class.getName());
      log.warn(e);

    } catch(UnsupportedOperationException e) {
      log.warnf("%s: API error while setting up XML parser. Check your JAXP version\n", XMLUtils.class.getName());
      log.warn(e);
    }

    return db;
  }

  /**
   * Returns a validating XML parser given an XSD (not DTD!).
   *
   * @param schemaFile File wit hXML schema
   * @return An XML parser in the form of a DocumentBuilder
   */
  public static DocumentBuilder getValidatingXmlParser(File schemaFile) {
    DocumentBuilder db = null;
    try {
      DocumentBuilderFactory dbf = safeDocumentBuilderFactory();

      SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
      Schema schema = factory.newSchema(schemaFile);
      dbf.setSchema(schema);

      db = dbf.newDocumentBuilder();
      db.setErrorHandler(new SAXErrorHandler());

    } catch (ParserConfigurationException e) {
      log.warnf("%s: Unable to create XML parser\n", XMLUtils.class.getName());
      log.warn(e);

    } catch (SAXException e) {
      log.warnf("%s: XML parsing exception while loading schema %s\n", XMLUtils.class.getName(),schemaFile.getPath());
      log.warn(e);

    } catch(UnsupportedOperationException e) {
      log.warnf("%s: API error while setting up XML parser. Check your JAXP version\n", XMLUtils.class.getName());
      log.warn(e);
    }

    return db;
  }

  /**
   * Block-level HTML tags that are rendered with surrounding line breaks.
   */
  private static final Set<String> breakingTags = Generics.newHashSet(Arrays.asList(new String[] {"blockquote", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "li", "ol", "p", "pre", "ul", "tr", "td"}));

  /**
   * @param r       the reader to read the XML/HTML from
   * @param mapBack a List of Integers mapping the positions in the result buffer
   *                to positions in the original Reader, will be cleared on receipt
   * @return the String containing the resulting text
   */
  public static String stripTags(Reader r, List<Integer> mapBack, boolean markLineBreaks) {
    if (mapBack != null) {
      mapBack.clear(); // just in case it has something in it!
    }
    StringBuilder result = new StringBuilder();
    try {
      int position = 0;
      do {
        String text = XMLUtils.readUntilTag(r);
        if (text.length() > 0) {
          // add offsets to the map back
          for (int i = 0; i < text.length(); i++) {
            result.append(text.charAt(i));
            if (mapBack != null) {
              mapBack.add(Integer.valueOf(position + i));
            }
          }
          position += text.length();
        }
        //        System.err.println(position + " got text: " + text);
        String tag = XMLUtils.readTag(r);
        if (tag == null) {
          break;
        }
        if (markLineBreaks && XMLUtils.isBreaking(parseTag(tag))) {
          result.append("\n");
          if (mapBack != null) {
            mapBack.add(Integer.valueOf(-position));
          }
        }
        position += tag.length();
        //        System.err.println(position + " got tag: " + tag);
      } while (true);
    } catch (IOException e) {
      log.warn("Error reading string");
      log.warn(e);
    }
    return result.toString();
  }

  public static boolean isBreaking(String tag) {
    return breakingTags.contains(tag);
  }

  public static boolean isBreaking(XMLTag tag) {
    return breakingTags.contains(tag.name);
  }

  /**
   * Reads all text up to next XML tag and returns it as a String.
   *
   * @return the String of the text read, which may be empty.
   */
  public static String readUntilTag(Reader r) throws IOException {
    if (!r.ready()) {
      return "";
    }
    StringBuilder b = new StringBuilder();
    int c = r.read();
    while (c >= 0 && c != '<') {
      b.append((char) c);
      c = r.read();
    }
    return b.toString();
  }

  /**
   * @return the new XMLTag object, or null if couldn't be created
   */
  public static XMLTag readAndParseTag(Reader r) throws IOException {
    String s = readTag(r);
    if (s == null) {
      return null;
    }
    XMLTag ret = null;
    try {
      ret = new XMLTag(s);
    } catch (Exception e) {
      log.warn("Failed to handle |" + s + "|");
    }
    return ret;
  }

  // Pattern is reentrant, going by the statement "many matchers can share the same pattern"
  // on the Pattern javadoc.  Therefore, this should be safe as a static final variable.
  private static final Pattern xmlEscapingPattern = Pattern.compile("&.+?;");

  public static String unescapeStringForXML(String s) {
    StringBuilder result = new StringBuilder();
    Matcher m = xmlEscapingPattern.matcher(s);
    int end = 0;
    while (m.find()) {
      int start = m.start();
      result.append(s, end, start);
      end = m.end();
      result.append(translate(s.substring(start, end)));
    }
    result.append(s, end, s.length());
    return result.toString();
  }

  private static char translate(String s) {
    switch (s) {
      case "&":
        return '&';
      case "<":
      case "≪":
        return '<';
      case ">":
      case "≫":
        return '>';
      case """:
        return '\"';
      case "'":
        return '\'';
      case "*":
        return '*';
      case "♯":
        return '\u266F';
      case "=":
        return '=';
      case " ":
        return (char) 0xA0;
      case "¡":
        return (char) 0xA1;
      case "¢":
      case "&shilling;":
        return (char) 0xA2;
      case "£":
        return (char) 0xA3;
      case "¤":
        return (char) 0xA4;
      case "¥":
        return (char) 0xA5;
      case "¦":
        return (char) 0xA6;
      case "§":
        return (char) 0xA7;
      case "¨":
        return (char) 0xA8;
      case "©":
        return (char) 0xA9;
      case "ª":
        return (char) 0xAA;
      case "« ":
        return (char) 0xAB;
      case "¬":
        return (char) 0xAC;
      case " ":
        return (char) 0xAD;
      case "®":
        return (char) 0xAE;
      case "¯":
        return (char) 0xAF;
      case "°":
        return (char) 0xB0;
      case "±":
        return (char) 0xB1;
      case "²":
        return (char) 0xB2;
      case "³":
        return (char) 0xB3;
      case "´":
        return (char) 0xB4;
      case "µ":
        return (char) 0xB5;
      case "·":
        return (char) 0xB7;
      case "¸":
        return (char) 0xB8;
      case "¹":
        return (char) 0xB9;
      case "º":
        return (char) 0xBA;
      case "»":
        return (char) 0xBB;
      case "¼ ":
        return (char) 0xBC;
      case "½":
        return (char) 0xBD;
      case "¾ ":
        return (char) 0xBE;
      case "¿":
        return (char) 0xBF;
      case "À":
        return (char) 0xC0;
      case "Á":
        return (char) 0xC1;
      case "Â":
        return (char) 0xC2;
      case "Ã":
        return (char) 0xC3;
      case "Ä":
        return (char) 0xC4;
      case "Å":
        return (char) 0xC5;
      case "Æ":
        return (char) 0xC6;
      case "Ç":
        return (char) 0xC7;
      case "È":
        return (char) 0xC8;
      case "É":
        return (char) 0xC9;
      case "Ê":
        return (char) 0xCA;
      case "Ë":
        return (char) 0xCB;
      case "Ì":
        return (char) 0xCC;
      case "Í":
        return (char) 0xCD;
      case "Î":
        return (char) 0xCE;
      case "Ï":
        return (char) 0xCF;
      case "Ð":
        return (char) 0xD0;
      case "Ñ":
        return (char) 0xD1;
      case "Ò":
        return (char) 0xD2;
      case "Ó":
        return (char) 0xD3;
      case "Ô":
        return (char) 0xD4;
      case "Õ":
        return (char) 0xD5;
      case "Ö":
        return (char) 0xD6;
      case "×":
        return (char) 0xD7;
      case "Ø":
        return (char) 0xD8;
      case "Ù":
        return (char) 0xD9;
      case "Ú":
        return (char) 0xDA;
      case "Û":
        return (char) 0xDB;
      case "Ü":
        return (char) 0xDC;
      case "Ý":
        return (char) 0xDD;
      case "Þ":
        return (char) 0xDE;
      case "ß":
        return (char) 0xDF;
      case "à":
        return (char) 0xE0;
      case "á":
        return (char) 0xE1;
      case "â":
        return (char) 0xE2;
      case "ã":
        return (char) 0xE3;
      case "ä":
        return (char) 0xE4;
      case "å":
        return (char) 0xE5;
      case "æ":
        return (char) 0xE6;
      case "ç":
        return (char) 0xE7;
      case "è":
        return (char) 0xE8;
      case "é":
        return (char) 0xE9;
      case "ê":
        return (char) 0xEA;
      case "ë ":
        return (char) 0xEB;
      case "ì":
        return (char) 0xEC;
      case "í":
        return (char) 0xED;
      case "î":
        return (char) 0xEE;
      case "ï":
        return 0xEF;
      case "ð":
        return (char) 0xF0;
      case "ñ":
        return (char) 0xF1;
      case "ò":
        return (char) 0xF2;
      case "ó":
        return (char) 0xF3;
      case "ô":
        return (char) 0xF4;
      case "õ":
        return (char) 0xF5;
      case "ö":
        return (char) 0xF6;
      case "÷":
        return (char) 0xF7;
      case "ø":
        return (char) 0xF8;
      case "ù":
        return (char) 0xF9;
      case "ú":
        return (char) 0xFA;
      case "û":
        return (char) 0xFB;
      case "ü":
        return (char) 0xFC;
      case "ý":
        return (char) 0xFD;
      case "þ":
        return (char) 0xFE;
      case "ÿ":
        return (char) 0xFF;
      case "Œ":
        return (char) 0x152;
      case "œ":
        return (char) 0x153;
      case "Š":
        return (char) 0x160;
      case "š":
        return (char) 0x161;
      case "Ÿ":
        return (char) 0x178;
      case "ˆ":
        return (char) 0x2C6;
      case "˜":
        return (char) 0x2DC;
      case "":
        return (char) 0x200E;
      case "":
        return (char) 0x200F;
      case "–":
        return (char) 0x2013;
      case "—":
        return (char) 0x2014;
      case "‘":
        return (char) 0x2018;
      case "’":
        return (char) 0x2019;
      case "‚":
        return (char) 0x201A;
      case "“":
      case "&bquo;":
      case "&bq;":
        return (char) 0x201C;
      case "”":
      case "&equo;":
        return (char) 0X201D;
      case "„":
        return (char) 0x201E;
      case "∼":
        return (char) 0x223C;
      case "√":
        return (char) 0x221A;
      case "≤":
        return (char) 0x2264;
      case "≥":
        return (char) 0x2265;
      case "←":
        return (char) 0x2190;
      case "↓":
        return (char) 0x2193;
      case "→":
        return (char) 0x2192;
      case "…":
        return (char) 0x2026;
      case "′":
        return (char) 0x2032;
      case "″":
      case "&ins;":
        return (char) 0x2033;
      case "™":
        return (char) 0x2122;
      case "Α":
      case "&Agr;":
        return (char) 0x391;
      case "Β":
      case "&Bgr;":
        return (char) 0x392;
      case "Γ":
      case "&Ggr;":
        return (char) 0x393;
      case "Δ":
      case "&Dgr;":
        return (char) 0x394;
      case "Ε":
      case "&Egr;":
        return (char) 0x395;
      case "Ζ":
      case "&Zgr;":
        return (char) 0x396;
      case "Η":
        return (char) 0x397;
      case "Θ":
      case "&THgr;":
        return (char) 0x398;
      case "Ι":
      case "&Igr;":
        return (char) 0x399;
      case "Κ":
      case "&Kgr;":
        return (char) 0x39A;
      case "Λ":
      case "&Lgr;":
        return (char) 0x39B;
      case "Μ":
      case "&Mgr;":
        return (char) 0x39C;
      case "Ν":
      case "&Ngr;":
        return (char) 0x39D;
      case "Ξ":
      case "&Xgr;":
        return (char) 0x39E;
      case "Ο":
      case "&Ogr;":
        return (char) 0x39F;
      case "Π":
      case "&Pgr;":
        return (char) 0x3A0;
      case "Ρ":
      case "&Rgr;":
        return (char) 0x3A1;
      case "Σ":
      case "&Sgr;":
        return (char) 0x3A3;
      case "Τ":
      case "&Tgr;":
        return (char) 0x3A4;
      case "Υ":
      case "&Ugr;":
        return (char) 0x3A5;
      case "Φ":
      case "&PHgr;":
        return (char) 0x3A6;
      case "Χ":
      case "&KHgr;":
        return (char) 0x3A7;
      case "Ψ":
      case "&PSgr;":
        return (char) 0x3A8;
      case "Ω":
      case "&OHgr;":
        return (char) 0x3A9;
      case "α":
      case "&agr;":
        return (char) 0x3B1;
      case "β":
      case "&bgr;":
        return (char) 0x3B2;
      case "γ":
      case "&ggr;":
        return (char) 0x3B3;
      case "δ":
      case "&dgr;":
        return (char) 0x3B4;
      case "ε":
      case "&egr;":
        return (char) 0x3B5;
      case "ζ":
      case "&zgr;":
        return (char) 0x3B6;
      case "η":
      case "&eegr;":
        return (char) 0x3B7;
      case "θ":
      case "&thgr;":
        return (char) 0x3B8;
      case "ι":
      case "&igr;":
        return (char) 0x3B9;
      case "κ":
      case "&kgr;":
        return (char) 0x3BA;
      case "λ":
      case "&lgr;":
        return (char) 0x3BB;
      case "μ":
      case "&mgr;":
        return (char) 0x3BC;
      case "ν":
      case "&ngr;":
        return (char) 0x3BD;
      case "ξ":
      case "&xgr;":
        return (char) 0x3BE;
      case "ο":
      case "&ogr;":
        return (char) 0x3BF;
      case "π":
      case "&pgr;":
        return (char) 0x3C0;
      case "ρ":
      case "&rgr;":
        return (char) 0x3C1;
      case "σ":
      case "&sgr;":
        return (char) 0x3C3;
      case "τ":
      case "&tgr;":
        return (char) 0x3C4;
      case "υ":
      case "&ugr;":
        return (char) 0x3C5;
      case "φ":
      case "&phgr;":
        return (char) 0x3C6;
      case "χ":
      case "&khgr;":
        return (char) 0x3C7;
      case "ψ":
      case "&psgr;":
        return (char) 0x3C8;
      case "ω":
      case "&ohgr;":
        return (char) 0x3C9;
      case "•":
        return (char) 0x2022;
      case "%":
        return '%';
      case "+":
        return '+';
      case "‐":
        return '-';
      case "ă":
      case "ā":
      case "≊":
      case "ą":
        return 'a';
      case "Ā":
        return 'A';
      case "ć":
      case "č":
      case "ĉ":
        return 'c';
      case "Č":
        return 'C';
      case "ď":
        return 'd';
      case "ě":
      case "ē":
      case "ę":
        return 'e';
      case "Ē":
      case "Ě":
        return 'E';
      case "ĺ":
        return 'l';
      case "Ĺ":
        return 'L';
      case "ń":
      case "ň":
      case "ņ":
        return 'n';
      case "ř":
      case "ŕ":
        return 'r';
      case "Ř":
        return 'R';
      case "ō":
        return 'o';
      case "ī":
        return 'i';
      case "ś":
      case "ş":
      case "ŝ":
        return 's';
      case "&Sacute":
      case "Ş":
        return 'S';
      case "ť":
      case "ţ":
        return 't';
      case "ū":
      case "ů":
        return 'u';
      case "ŵ":
        return 'w';
      case "Ŷ":
        return 'Y';
      case "ŷ":
        return 'y';
      case "ž":
      case "ź":
        return 'z';
      case "Ž":
        return 'Z';
      case "♥":
        return (char) 0x2665;
      case "∞":
        return (char) 0x221E;
      case "$":
        return '$';
      case "⊂":
      case "{":
        return (char) 0x2282;
      case "⊃":
      case "}":
        return (char) 0x2283;
      case "[":
        return '[';
      case "]":
        return ']';
      default:
        return ' ';
    }
  }

  /** Returns a String in which all the XML special characters have been
   *  escaped. The resulting String is valid to print in an XML file as an
   *  attribute or element value in all circumstances.  (Note that it may
   *  escape characters that didn't need to be escaped.)
   *
   *  @param in The String to escape
   *  @return The escaped String
   */
  public static String escapeXML(String in) {
    int leng = in.length();
    StringBuilder sb = new StringBuilder(leng);
    for (int i = 0; i < leng; i++) {
      char c = in.charAt(i);
      if (c == '&') {
        sb.append("&");
      } else if (c == '<') {
        sb.append("<");
      } else if (c == '>') {
        sb.append(">");
      } else if (c == '"') {
        sb.append(""");
      } else if (c == '\'') {
        sb.append("'");
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  /** Returns a String in which some the XML special characters have been
   *  escaped: just the ones that need escaping in an element content.
   *
   *  @param in The String to escape
   *  @return The escaped String
   */
  public static String escapeElementXML(String in) {
    int leng = in.length();
    StringBuilder sb = new StringBuilder(leng);
    for (int i = 0; i < leng; i++) {
      char c = in.charAt(i);
      if (c == '&') {
        sb.append("&");
      } else if (c == '<') {
        sb.append("<");
      } else if (c == '>') {
        sb.append(">");
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  /** Returns a String in which some XML special characters have been
   *  escaped. This just escapes attribute value ones, assuming that
   *  you're going to quote with double quotes.
   *  That is, only " and & are escaped.
   *
   *  @param in The String to escape
   *  @return The escaped String
   */
  public static String escapeAttributeXML(String in) {
    int leng = in.length();
    StringBuilder sb = new StringBuilder(leng);
    for (int i = 0; i < leng; i++) {
      char c = in.charAt(i);
      if (c == '&') {
        sb.append("&");
      } else if (c == '"') {
        sb.append(""");
      } else {
        sb.append(c);
      }
    }
    return sb.toString();
  }

  public static String escapeTextAroundXMLTags(String s) {
    StringBuilder result = new StringBuilder();
    Reader r = new StringReader(s);
    try {
      do {
        String text = readUntilTag(r);
        //      System.err.println("got text: " + text);
        result.append(escapeXML(text));
        XMLTag tag = readAndParseTag(r);
        //      System.err.println("got tag: " + tag);
        if (tag == null) {
          break;
        }
        result.append(tag);
      } while (true);
    } catch (IOException e) {
      log.warn("Error reading string");
      log.warn(e);
    }
    return result.toString();
  }

  /**
   * return either the first space or the first nbsp
   */
  public static int findSpace(String haystack, int begin) {
    int space = haystack.indexOf(' ', begin);
    int nbsp = haystack.indexOf('\u00A0', begin);
    if (space == -1 && nbsp == -1) {
      return -1;
    } else if (space >= 0 && nbsp >= 0) {
      return Math.min(space, nbsp);
    } else {
      // eg one is -1, and the other is >= 0
      return Math.max(space, nbsp);
    }
  }

  public static class XMLTag {

    /** Stores the complete string passed in as the tag on construction. */
    public String text;

    /** Stores the element name, such as "doc". */
    public String name;

    /** Stores attributes as a Map from keys to values. */
    public Map<String,String> attributes;

    /** Whether this is an ending tag or not. */
    public boolean isEndTag;

    /** Whether this is an empty element expressed as a single empty element tag like {@code <p/>}. */
    public boolean isSingleTag;

    /**
     * Assumes that String contains an XML tag.
     *
     * @param tag String to turn into an XMLTag object
     */
    public XMLTag(String tag) {
      if (tag == null || tag.isEmpty()) {
        throw new NullPointerException("Attempted to parse empty/null tag");
      }
      if (tag.charAt(0) != '<') {
        throw new IllegalArgumentException("Tag did not start with <");
      }
      if (tag.charAt(tag.length() - 1) != '>') {
        throw new IllegalArgumentException("Tag did not end with >");
      }
      text = tag;
      int begin = 1;
      if (tag.charAt(1) == '/') {
        begin = 2;
        isEndTag = true;
      } else {
        isEndTag = false;
      }
      int end = tag.length() - 1;
      if (tag.charAt(tag.length() - 2) == '/') {
        end = tag.length() - 2;
        isSingleTag = true;
      } else {
        isSingleTag = false;
      }
      tag = tag.substring(begin, end);
      attributes = Generics.newHashMap();
      begin = 0;
      end = findSpace(tag, 0);

      if (end < 0) {
        name = tag;
      } else {
        name = tag.substring(begin, end);
        do {
          begin = end + 1;
          while (begin < tag.length() && tag.charAt(begin) < 0x21) {
            begin++; // get rid of leading whitespace
          }
          if (begin == tag.length()) {
            break;
          }
          end = tag.indexOf('=', begin);
          if (end < 0) {
            String att = tag.substring(begin);
            attributes.put(att, "");
            break;
          }
          String att = tag.substring(begin, end).trim();
          begin = end + 1;
          String value = null;
          if (tag.length() > begin) {
            while (begin < tag.length() && tag.charAt(begin) < 0x21) {
              begin++;
            }
            if (begin < tag.length() && tag.charAt(begin) == '\"') {
              // get quoted expression
              begin++;
              end = tag.indexOf('\"', begin);
              if (end < 0) {
                break; // this is a problem
              }
              value = tag.substring(begin, end);
              end++;
            } else {
              // get unquoted expression
              end = findSpace(tag, begin);
              if (end < 0) {
                end = tag.length();
              }
//              System.err.println(begin + " " + end);
              value = tag.substring(begin, end);
            }
          }
          attributes.put(att, value);
        } while (end < tag.length() - 3);
      }
    }

    public String toString() {
      return text;
    }

    /**
     * Given a list of attributes, return the first one that is non-null
     */
    public String getFirstNonNullAttributeFromList(List<String> attributesList) {
      for (String attribute : attributesList) {
        if (attributes.get(attribute) != null) {
          return attributes.get(attribute);
        }
      }
      return null;
    }
  } // end static class XMLTag

  /**
   * Reads all text of the XML tag and returns it as a String.
   * Assumes that a '<' character has already been read.
   *
   * @param r The reader to read from
   * @return The String representing the tag, or null if one couldn't be read
   *         (i.e., EOF).  The returned item is a complete tag including angle
   *         brackets, such as {@code <TXT>}
   */
  public static String readTag(Reader r) throws IOException {
    if ( ! r.ready()) {
      return null;
    }
    StringBuilder b = new StringBuilder("<");
    int c = r.read();
    while (c >= 0) {
      b.append((char) c);
      if (c == '>') {
        break;
      }
      c = r.read();
    }
    if (b.length() == 1) {
      return null;
    }
    return b.toString();
  }

  public static XMLTag parseTag(String tagString) {
    if (tagString == null || tagString.isEmpty()) {
      return null;
    }
    if (tagString.charAt(0) != '<' ||
        tagString.charAt(tagString.length() - 1) != '>') {
      return null;
    }
    return new XMLTag(tagString);
  }

  public static Document readDocumentFromFile(String filename) throws Exception {
    InputSource in = new InputSource(new FileReader(filename));
    DocumentBuilderFactory factory = safeDocumentBuilderFactory();

    factory.setNamespaceAware(false);
    DocumentBuilder db = factory.newDocumentBuilder();
    db.setErrorHandler(new SAXErrorHandler());
    return db.parse(in);
  }

  private static class SAXErrorHandler implements ErrorHandler {

    public static String makeBetterErrorString(String msg,
                                               SAXParseException ex) {
      StringBuilder sb = new StringBuilder(msg);
      sb.append(": ");
      String str = ex.getMessage();
      if (str.lastIndexOf('.') == str.length() - 1) {
        str = str.substring(0, str.length() - 1);
      }
      sb.append(str);
      sb.append(" at document line ").append(ex.getLineNumber());
      sb.append(", column ").append(ex.getColumnNumber());
      if (ex.getSystemId() != null) {
        sb.append(" in entity from systemID ").append(ex.getSystemId());
      } else if (ex.getPublicId() != null) {
        sb.append(" in entity from publicID ").append(ex.getPublicId());
      }
      sb.append('.');
      return sb.toString();
    }

    @Override
    public void warning(SAXParseException exception) {
      log.warn(makeBetterErrorString("Warning", exception));
    }

    @Override
    public void error(SAXParseException exception) {
      log.error(makeBetterErrorString("Error", exception));
    }

    @Override
    public void fatalError(SAXParseException ex) throws SAXParseException {
      throw new SAXParseException(makeBetterErrorString("Fatal Error", ex),
              ex.getPublicId(), ex.getSystemId(), ex.getLineNumber(), ex.getColumnNumber());
      // throw new RuntimeException(makeBetterErrorString("Fatal Error", ex));
    }

  } // end class SAXErrorHandler

  public static Document readDocumentFromString(String s) throws Exception {
    InputSource in = new InputSource(new StringReader(s));
    DocumentBuilderFactory factory = safeDocumentBuilderFactory();
    factory.setNamespaceAware(false);
    return factory.newDocumentBuilder().parse(in);
  }

  /** Tests a few methods.
   *  If the first arg is -readDoc then this method tests
   *  readDocumentFromFile.
   *  Otherwise, it tests readTag/readUntilTag and slurpFile.
   */
  public static void main(String[] args) throws Exception {
    if (args[0].equals("-readDoc")) {
      Document doc = readDocumentFromFile(args[1]);
      System.out.println(doc);
    } else {
      String s = IOUtils.slurpFile(args[0]);
      Reader r = new StringReader(s);
      String tag = readTag(r);
      while (tag != null && ! tag.isEmpty()) {
        readUntilTag(r);
        tag = readTag(r);
        if (tag == null || tag.isEmpty()) {
          break;
        }
        System.out.println("got tag=" + new XMLTag(tag));
      }
    }
  }

}

Output:
【漏洞类型】XML Schema处理安全漏洞

【严重性】中等

【漏洞摘要】在`getValidatingXmlParser`函数中，虽然使用了`safeDocumentBuilderFactory`来设置XML解析器的安全特性，但`SchemaFactory`对象没有启用安全处理特性。具体来说，`SchemaFactory`没有设置`XMLConstants.FEATURE_SECURE_PROCESSING`为`true`。这可能导致在解析XML Schema时存在安全漏洞，例如外部实体注入攻击。

【关键代码模式】
- `getValidatingXmlParser`函数中的`SchemaFactory`对象没有启用安全处理特性。
- 与XML解析相关的`DocumentBuilderFactory`和`SchemaFactory`的使用。
- `safeDocumentBuilderFactory`函数中设置的安全特性，包括禁用DTD和外部实体。

【修复方法说明】在`SchemaFactory`对象上启用安全处理特性，以防止潜在的安全攻击。通过调用`factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);`，可以确保`SchemaFactory`在解析XML Schema时遵循安全处理规则，从而减少安全风险。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/edu/stanford/nlp/process/TransformXML.java
### 代码内容：
package edu.stanford.nlp.process;
import edu.stanford.nlp.util.logging.Redwood;

import java.io.*;
import java.util.*;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.function.Function;
import edu.stanford.nlp.io.IOUtils;
import edu.stanford.nlp.util.XMLUtils;

/**
 * Reads XML from an input file or stream and writes XML to an output
 * file or stream, while transforming text appearing inside specified
 * XML tags by applying a specified {@link Function
 * Function}.  See TransformXMLApplications for examples.
 * Implementation note: This is done using SAX2.
 *
 * @param <T> The type of the output of the Function (from String to T)
 * @author Bill MacCartney
 * @author Anna Rafferty (refactoring, making SAXInterface easy to extend elsewhere)
 */
public class TransformXML<T>  {

  /** A logger for this class */
  private static Redwood.RedwoodChannels log = Redwood.channels(TransformXML.class);

  private final SAXParser saxParser;

  public SAXInterface<T> buildSaxInterface() { return new SAXInterface<>(); }

  public static class SAXInterface<T> extends DefaultHandler {

    protected List<String> elementsToBeTransformed;
    protected StringBuilder textToBeTransformed;
    protected PrintWriter outWriter = new PrintWriter(System.out, true);
    protected Function<String,T> function;

    /**
     * How far down we are in the nested tags.  For example, if we've
     * seen <foo> <bar> and "foo" and "bar" are both tags
     * we care about, then depth = 2.
     */
    protected int depth = 0;

    public SAXInterface() {
      elementsToBeTransformed = new ArrayList<>();
      depth = 0;
      openingTag = null;
      textToBeTransformed = new StringBuilder();
    }

    /**
     * The first tag from {@link elementsToBeTransformed}
     * that we saw the last time {@link depth} was
     * 0.
     *

     * You would expect incoming XML to be well-formatted, but just in
     * case it isn't, we keep track of this so we can output the
     * correct closing tag.
     */
    String openingTag;

    private void outputTextAndTag(String qName, Attributes attributes, boolean close) {
      // If we're not already in an element to be transformed, first
      // echo the previous text...
      outWriter.print(XMLUtils.escapeXML(textToBeTransformed.toString()));
      textToBeTransformed = new StringBuilder();

      // ... then echo the new tag to outStream
      outWriter.print('<');
      if (close) {
        outWriter.print('/');
      }
      outWriter.print(qName);
      if (attributes != null) {
        for (int i = 0; i < attributes.getLength(); i++) {
          outWriter.print(' ');
          outWriter.print(attributes.getQName(i));
          outWriter.print("=\"");
          outWriter.print(XMLUtils.escapeXML(attributes.getValue(i)));
          outWriter.print('"');
        }
      }
      outWriter.print(">\n");
    }

    @Override
    public void endDocument() {
      // Theoretically, there shouldn't be anything in the buffer after
      // the last closing tag, but if there is, it's probably better to
      // echo it than ignore it
      outWriter.print(XMLUtils.escapeXML(textToBeTransformed.toString()));
      // we need to flush because there are no other ways we
      // explicitely flush
      outWriter.flush();
    }

    // Called at the beginning of each element.  If the tag is on the
    // designated list, set flag to remember that we're in an element
    // to be transformed.  In either case, echo tag.
    @Override
    public void startElement(String uri, String localName, String qName,
                             Attributes attributes) throws SAXException {
      //log.info("start element " + qName);

      if (depth == 0) {
        outputTextAndTag(qName, attributes, false);
      }

      if (elementsToBeTransformed.contains(qName)) {
        if (depth == 0) {
          openingTag = qName;
        }
        ++depth;
      }
    }

    // Called at the end of each element.  If the tag is on the
    // designated list, apply the designated {@link Function
    // Function} to the accumulated text and echo the the
    // result.  In either case, echo the closing tag.
    @Override
    public void endElement(String uri, String localName, String qName)
      throws SAXException
    {
      //log.info("end element " + qName + "; function is " + function.getClass());
      //log.info("elementsToBeTransformed is " + elementsToBeTransformed);
      //log.info("textToBeTransformed is " + textToBeTransformed);

      if (depth == 0) {
        outputTextAndTag(qName, null, true);
      } else {
        if (elementsToBeTransformed.contains(qName)) {
          --depth;
          if (depth == 0) {
            String text = textToBeTransformed.toString().trim();
            // factored out so subclasses can handle the text differently
            processText(text);
            textToBeTransformed = new StringBuilder();
            outWriter.print("</" + openingTag + ">\n");
          }
        }
        // when we're inside a block to be transformed, we ignore
        // elements that don't end the block.
      }
    }

    public void processText(String text) {
      if (text.length() > 0) {
        text = function.apply(text).toString();
        outWriter.print(XMLUtils.escapeXML(text));
        outWriter.print('\n');
      }
    }

    // Accumulate characters in buffer of text to be transformed
    // (SAX may call this after each line break)
    @Override
    public void characters(char[] buf, int offset, int len) throws SAXException {
      // log.info("characters |" + new String(buf, offset, len) + "|");
      textToBeTransformed.append(buf, offset, len);
    }
  } // end static class SAXInterface

  /**
   * This version of the SAXInterface doesn't escape the text produced
   * by the function.  This is useful in the case where the function
   * already produces well-formed XML.  One example of this is the
   * Tagger, which already escapes the inner text and produces xml
   * tags around the words.
   */
  public static class NoEscapingSAXInterface<T> extends SAXInterface<T> {
    @Override
    public void processText(String text) {
      if (text.length() > 0) {
        text = function.apply(text).toString();
        outWriter.print(text);
        outWriter.print('\n');
      }
    }
  }

  public TransformXML() {
    try {
      saxParser = SAXParserFactory.newInstance().newSAXParser();
    } catch (Exception e) {
      log.info("Error configuring XML parser: " + e);
      throw new RuntimeException(e);
    }
  }

  /**
   * Read XML from the specified file and write XML to stdout,
   * while transforming text appearing inside the specified XML
   * tags by applying the specified {@link Function
   * Function}.  Note that the Function
   * you supply must be prepared to accept Strings as
   * input; if your Function doesn't handle
   * Strings, you need to write a wrapper for it that
   * does.
   *
   * @param tags an array of Strings, each an XML tag
   *             within which the transformation should be applied
   * @param fn   the {@link Function Function} to apply
   * @param in   the File to read from
   */
  public void transformXML(String[] tags, Function<String,T> fn, File in) {
    InputStream ins = null;
    try {
      ins = new BufferedInputStream(new FileInputStream(in));
      transformXML(tags, fn, ins, System.out);
    } catch (Exception e) {
      log.info("Error reading file " + in + ": " + e);
      e.printStackTrace();
    } finally {
      IOUtils.closeIgnoringExceptions(ins);
    }
  }

  /**
   * Read XML from the specified file and write XML to specified file,
   * while transforming text appearing inside the specified XML tags
   * by applying the specified {@link Function Function}.
   * Note that the Function you supply must be
   * prepared to accept Strings as input; if your
   * Function doesn't handle Strings, you
   * need to write a wrapper for it that does.
   *
   * @param tags an array of Strings, each an XML tag
   *             within which the transformation should be applied
   * @param fn   the {@link Function Function} to apply
   * @param in   the File to read from
   * @param out  the File to write to
   */
  public void transformXML(String[] tags, Function<String,T> fn, File in, File out) {
    InputStream ins = null;
    OutputStream outs = null;
    try {
      ins = new BufferedInputStream(new FileInputStream(in));
      outs = new BufferedOutputStream(new FileOutputStream(out));
      transformXML(tags, fn, ins, outs);
    } catch (Exception e) {
      log.info("Error reading file " + in + " or writing file " + out + ": " + e);
      e.printStackTrace();
    } finally {
      IOUtils.closeIgnoringExceptions(ins);
      IOUtils.closeIgnoringExceptions(outs);
    }
  }

  /**
   * Read XML from input stream and write XML to stdout, while
   * transforming text appearing inside the specified XML tags by
   * applying the specified {@link Function Function}.
   * Note that the Function you supply must be
   * prepared to accept Strings as input; if your
   * Function doesn't handle Strings, you
   * need to write a wrapper for it that does.
   *
   * @param tags an array of Strings, each an XML tag
   *             within which the transformation should be applied
   * @param fn   the {@link Function Function} to apply
   * @param in   the InputStream to read from
   */
  public void transformXML(String[] tags, Function<String,T> fn, InputStream in) {
    transformXML(tags, fn, in, System.out);
  }

  /**
   * Read XML from input stream and write XML to output stream,
   * while transforming text appearing inside the specified XML tags
   * by applying the specified {@link Function Function}.
   * Note that the Function you supply must be
   * prepared to accept Strings as input; if your
   * Function doesn't handle Strings, you
   * need to write a wrapper for it that does.
   *
   * @param tags an array of Strings, each an XML tag
   *             within which the transformation should be applied
   * @param fn   the {@link Function Function} to apply
   * @param in   the InputStream to read from
   * @param out  the OutputStream to write to
   */
  public void transformXML(String[] tags, Function<String,T> fn, InputStream in, OutputStream out) {
    transformXML(tags, fn, in, new OutputStreamWriter(out),
                 buildSaxInterface());
  }

  /**
   * Read XML from input stream and write XML to output stream,
   * while transforming text appearing inside the specified XML tags
   * by applying the specified {@link Function Function}.
   * Note that the Function you supply must be
   * prepared to accept Strings as input; if your
   * Function doesn't handle Strings, you
   * need to write a wrapper for it that does.
   *
Implementation notes: The InputStream is assumed to already
   * be buffered if useful, and we need a stream, so that the XML decoder
   * can determine the correct character encoding of the XML file. The output
   * is to a Writer, and the provided Writer should again be buffered if
   * desirable.  Internally, this Writer is wrapped as a PrintWriter.
   *
   * @param tags an array of Strings, each an XML entity
   *             within which the transformation should be applied
   * @param fn   the {@link Function Function} to apply
   * @param in   the InputStream to read from
   * @param w    the Writer to write to
   */
  public void transformXML(String[] tags, Function<String,T> fn, InputStream in, Writer w) {
    transformXML(tags, fn, in, w, buildSaxInterface());
  }

  /**
   * Calls the fully specified transformXML with an InputSource
   * constructed from in.
   */
  public void transformXML(String[] tags, Function<String,T> fn, InputStream in, Writer w, SAXInterface<T> handler) {
    transformXML(tags, fn, new InputSource(in), w, handler);
  }

  /**
   * Calls the fully specified transformXML with an InputSource
   * constructed from in.
   */
  public void transformXML(String[] tags, Function<String,T> fn, Reader in, Writer w, SAXInterface<T> handler) {
    transformXML(tags, fn, new InputSource(in), w, handler);
  }

  /**
   * Read XML from input source and write XML to output writer,
   * while transforming text appearing inside the specified XML tags
   * by applying the specified {@link Function Function}.
   * Note that the Function you supply must be
   * prepared to accept Strings as input; if your
   * Function doesn't handle Strings, you
   * need to write a wrapper for it that does.
   *

   *
Implementation notes: The InputSource is assumed to already
   * be buffered if useful, and we need a stream, so that the XML decoder
   * can determine the correct character encoding of the XML file.
   * TODO: does that mean there's a bug if you send it a Reader
   * instead of an InputStream?  It seems to work with a Reader...
   *

   * The output is to a Writer, and the provided Writer should again
   * be buffered if desirable.  Internally, this Writer is wrapped as
   * a PrintWriter.
   *
   * @param tags an array of Strings, each an XML entity
   *             within which the transformation should be applied
   * @param fn   the {@link Function Function} to apply
   * @param in   the InputStream to read from
   * @param w    the Writer to write to
   * @param saxInterface the sax handler you would like to use (default is SaxInterface, defined in this class, but you may define your own handler)
   */
  public void transformXML(String[] tags, Function<String,T> fn, InputSource in, Writer w, SAXInterface<T> saxInterface) {
    saxInterface.outWriter = new PrintWriter(w, true);
    saxInterface.function = fn;
    saxInterface.elementsToBeTransformed = new ArrayList<>();
    saxInterface.elementsToBeTransformed.addAll(Arrays.asList(tags));
    try {
      saxParser.parse(in, saxInterface);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

} // end class TransformXML

Output:
【漏洞类型】XML外部实体攻击（XXE）
【严重性】High
【漏洞摘要】这是一个XML外部实体攻击漏洞。在`TransformXML`类的构造函数中，`SAXParser`实例化时没有设置`XMLConstants.FEATURE_SECURE_PROCESSING`选项为`true`，这意味着默认情况下，`SAXParser`可能允许加载和解析外部实体，这可能会导致服务器资源耗尽、远程代码执行或信息泄露等安全问题。
【关键代码模式】
- `SAXParser`实例化
- 未设置`XMLConstants.FEATURE_SECURE_PROCESSING`选项
【修复方法说明】修复方法通过在创建`SAXParserFactory`实例后设置`XMLConstants.FEATURE_SECURE_PROCESSING`选项为`true`，来禁用外部实体解析，从而防止XXE攻击。