Merge pull request #109 from GameServerPanel/copilot/fix-broken-xml-in-dayz-configs
Fix broken XML in DayZ game configs; add CDATA auto-sanitizer to XML editor
This commit is contained in:
commit
13de7c8383
3 changed files with 72 additions and 16 deletions
|
|
@ -169,6 +169,60 @@ function config_games_validate_xml_file(string $config_file): array
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Script-like element names whose text content is shell/batch code.
|
||||
* These nodes should be stored as CDATA sections so that characters such as
|
||||
* '<', '>', '&', etc. survive round-trips through the XML parser unchanged.
|
||||
*/
|
||||
function config_games_script_node_names(): array
|
||||
{
|
||||
return ['pre_install', 'post_install', 'pre_start', 'post_start', 'precmd', 'postcmd'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-sanitize raw XML text: for every script-like element whose text content
|
||||
* contains a bare '<' outside an existing CDATA block, wrap that content in a
|
||||
* CDATA section so the file becomes well-formed. Non-script elements are left
|
||||
* untouched.
|
||||
*
|
||||
* Assumptions / limitations:
|
||||
* - Script elements are treated as leaf nodes (no child elements expected).
|
||||
* Nested XML tags inside a script block are not supported and the regex
|
||||
* will not handle them correctly.
|
||||
* - The detection of an already-present CDATA section relies on the opening
|
||||
* tag being immediately followed by '<![CDATA[' (with optional whitespace).
|
||||
* If a script block mixes text and CDATA it is left unchanged.
|
||||
*
|
||||
* This is applied to raw XML submitted through the editor's "Raw XML" path
|
||||
* before the validation step, giving a best-effort fix rather than a hard
|
||||
* rejection for the most common authoring mistake.
|
||||
*
|
||||
* @param string $xml Raw XML string (may be malformed).
|
||||
* @return string XML string with script content wrapped in CDATA where needed.
|
||||
*/
|
||||
function config_games_sanitize_xml_scripts(string $xml): string
|
||||
{
|
||||
$tags = config_games_script_node_names();
|
||||
foreach ($tags as $tag) {
|
||||
$xml = preg_replace_callback(
|
||||
'/<' . preg_quote($tag, '/') . '(\s[^>]*)?>(?!\s*<!\[CDATA\[)(.*?)<\/' . preg_quote($tag, '/') . '>/si',
|
||||
function ($m) use ($tag) {
|
||||
$attrs = $m[1];
|
||||
$content = $m[2];
|
||||
// Only wrap if the content contains a raw '<' character. XML
|
||||
// entities such as < do not contain a literal '<' so they
|
||||
// are not matched here and do not trigger CDATA wrapping.
|
||||
if (strpos($content, '<') === false) {
|
||||
return $m[0];
|
||||
}
|
||||
return '<' . $tag . $attrs . '><![CDATA[' . $content . ']]></' . $tag . '>';
|
||||
},
|
||||
$xml
|
||||
);
|
||||
}
|
||||
return $xml;
|
||||
}
|
||||
|
||||
function config_games_print_editor_css()
|
||||
{
|
||||
static $printed = false;
|
||||
|
|
@ -388,13 +442,19 @@ function config_games_save_xml($db, $home_cfg_id, array $nodesPayload)
|
|||
continue;
|
||||
}
|
||||
$hasChildren = !empty($nodeData['has_children']);
|
||||
$nodeName = strtolower(($slashPos = strrpos($path, '/')) !== false ? substr($path, $slashPos + 1) : $path);
|
||||
$isScriptNode = in_array($nodeName, config_games_script_node_names(), true);
|
||||
if (array_key_exists('value', (array)$nodeData)) {
|
||||
$normalizedValue = config_games_normalize_newlines($nodeData['value']);
|
||||
while ($domNode->firstChild) {
|
||||
$domNode->removeChild($domNode->firstChild);
|
||||
}
|
||||
if ($normalizedValue !== '') {
|
||||
$domNode->appendChild($dom->createTextNode($normalizedValue));
|
||||
if ($isScriptNode) {
|
||||
$domNode->appendChild($dom->createCDATASection($normalizedValue));
|
||||
} else {
|
||||
$domNode->appendChild($dom->createTextNode($normalizedValue));
|
||||
}
|
||||
}
|
||||
} elseif (!$hasChildren) {
|
||||
while ($domNode->firstChild) {
|
||||
|
|
@ -530,9 +590,11 @@ function exec_ogp_module() {
|
|||
if ($cfg_info !== FALSE) {
|
||||
$config_file = SERVER_CONFIG_LOCATION . $cfg_info['home_cfg_file'];
|
||||
$raw_content = $_POST['raw_xml_content'];
|
||||
// Apply best-effort auto-fix: wrap bare '<' chars in script blocks with CDATA.
|
||||
$sanitized_content = config_games_sanitize_xml_scripts($raw_content);
|
||||
// Write to a temp file for validation
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'gsp_xml_');
|
||||
file_put_contents($tmp, $raw_content);
|
||||
file_put_contents($tmp, $sanitized_content);
|
||||
$xmlErrors = config_games_validate_xml_file($tmp);
|
||||
@unlink($tmp);
|
||||
if (!empty($xmlErrors)) {
|
||||
|
|
@ -542,7 +604,7 @@ function exec_ogp_module() {
|
|||
}
|
||||
echo "</ul></div>";
|
||||
} else {
|
||||
if (file_put_contents($config_file, $raw_content) !== false) {
|
||||
if (file_put_contents($config_file, $sanitized_content) !== false) {
|
||||
print_success(get_lang('configs_updated_ok'));
|
||||
$config = read_server_config($config_file);
|
||||
if ($config !== FALSE) {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Make sure if you install a MOD, you list the name here or else it wont get loade
|
|||
</param>
|
||||
</server_params>
|
||||
|
||||
<post_install>
|
||||
<post_install><![CDATA[
|
||||
|
||||
mkdir -p ./cfg
|
||||
touch ./cfg/dayz_arma2co_win32.xml.txt
|
||||
|
|
@ -81,7 +81,7 @@ rm -f dayzmod1.9.0.tar
|
|||
|
||||
#Create Database ---------------------------------------
|
||||
|
||||
dbPass=$(</dev/urandom tr -dc 'A-Za-z0-9_' | head -c 12)
|
||||
dbPass=$(tr -dc 'A-Za-z0-9_' </dev/urandom | head -c 12)
|
||||
srvID=${PWD##*/}
|
||||
dbID="server_${srvID}"
|
||||
|
||||
|
|
@ -105,8 +105,6 @@ VALUES (1, ${srvID}, '${dbID}', '${dbPass}', '${dbID}', 1);
|
|||
mysql --force "${dbID}" < 1.9.0_fresh.sql
|
||||
|
||||
|
||||
|
||||
|
||||
# Create alsoRun.bat -----------------------------------
|
||||
|
||||
printf '%s\r\n' \
|
||||
|
|
@ -119,8 +117,7 @@ printf '%s\r\n' \
|
|||
'for /f "tokens=2 delims==" %%P in ('"'"'wmic process where "ExecutablePath='"'"'%cd:\=\\%\\bec.exe'"'"'" get ProcessId /value ^| find "="'"'"') do >"..\_alsoRun.pid" echo %%P' \
|
||||
> _alsoRun.bat
|
||||
|
||||
|
||||
</post_install>
|
||||
]]></post_install>
|
||||
<pre_start>
|
||||
</pre_start>
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Make sure if you install a MOD, you list the name here or else it wont get loade
|
|||
</param>
|
||||
</server_params>
|
||||
|
||||
<post_install>
|
||||
<post_install><![CDATA[
|
||||
|
||||
mkdir -p ./cfg
|
||||
touch ./cfg/epochmod_win32.xml
|
||||
|
|
@ -81,7 +81,7 @@ rm -f epochmod.tar
|
|||
|
||||
#Create Database ---------------------------------------
|
||||
|
||||
dbPass=$(</dev/urandom tr -dc 'A-Za-z0-9_' | head -c 12)
|
||||
dbPass=$(tr -dc 'A-Za-z0-9_' </dev/urandom | head -c 12)
|
||||
srvID=${PWD##*/}
|
||||
dbID="server_${srvID}"
|
||||
|
||||
|
|
@ -102,9 +102,7 @@ INSERT INTO panel.gsp_mysql_databases
|
|||
VALUES (1, ${srvID}, '${dbID}', '${dbPass}', '${dbID}', 1);
|
||||
"
|
||||
|
||||
mysql --force "${dbID}" < 1.9.0_fresh.sql
|
||||
|
||||
|
||||
mysql --force "${dbID}" < 1.9.0_fresh.sql
|
||||
|
||||
|
||||
# Create _alsoRun.bat -----------------------------------
|
||||
|
|
@ -119,8 +117,7 @@ printf '%s\r\n' \
|
|||
'for /f "tokens=2 delims==" %%P in ('"'"'wmic process where "ExecutablePath='"'"'%cd:\=\\%\\bec.exe'"'"'" get ProcessId /value ^| find "="'"'"') do >"..\_alsoRun.pid" echo %%P' \
|
||||
> _alsoRun.bat
|
||||
|
||||
|
||||
</post_install>
|
||||
]]></post_install>
|
||||
<pre_start>
|
||||
</pre_start>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue