_qt_internal_process_resource: Properly escape XML

XML requires escaping for certain characters, and we need to consider
this when writing out qrc files (which use XML).

This commit introduces a helper function,
_qt_internal_escape_xml_characters, to take care of the escaping.
It uses regular expressions to process the input strings. We take care
to start with '&', as '&' needs to be escaped, too.

We minimize the amount of escaping we're doing (the exact rules
differing between attributes and text), to avoid unnecessary work that
needs to be done when configuring a project. This is achieved by a
SUBSET option which can be passed to _qt_internal_escape_xml_characters.

Task-number: QTBUG-131916
Change-Id: Ic1bd0eedee0343c3d70b6954842e21b3c550b092
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
(cherry picked from commit e4fbbdea05540723d4c4429d673d25efa3201d7a)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 6e35353c83925d7c0d5f90db320ef9621f1ffb94)
This commit is contained in:
Fabian Kosmale 2025-01-22 13:13:27 +01:00 committed by Qt Cherry-pick Bot
parent b9206f1843
commit 238868c518
4 changed files with 81 additions and 4 deletions

View File

@ -2218,6 +2218,47 @@ function(_qt_internal_expose_deferred_files_to_ide target)
${scope_args} PROPERTIES HEADER_FILE_ONLY ON)
endfunction()
#
# Takes a string, and writes its XML escaped form into output_variable
# If SUBSET is given, only the characters in it will be escaped.
# No validation is done whether the characters in SUBSET are actually
# characters that need to be replaced
function(_qt_internal_escape_xml_characters input output_variable)
set(no_value_options "")
set(single_value_options SUBSET)
set(multi_value_options "")
cmake_parse_arguments(PARSE_ARGV 2 arg
"${no_value_options}"
"${single_value_options}"
"${multi_value_options}"
)
set(escaped "${input}")
# it is vital to start with &
# as later replacements add new &
set(chars & [["]] [[']] < >)
set(replacements &amp &quot &apos &lt &gt)
if (NOT arg_SUBSET)
set(subset [[&"'<>]])
else()
set(subset "${arg_SUBSET}")
endif()
foreach(i RANGE 4)
list(GET chars ${i} char)
list(GET replacements ${i} replacement)
string(FIND "${subset}" "${char}" pos)
if (${pos} EQUAL -1)
continue()
endif()
string(REGEX REPLACE
"${char}"
"${replacement};"
escaped
"${escaped}"
)
endforeach()
set(${output_variable} "${escaped}" PARENT_SCOPE)
endfunction()
#
# Process resources via file path instead of QRC files. Behind the
# scenes, it will generate a qrc file.
@ -2314,8 +2355,12 @@ function(_qt_internal_process_resource target resourceName)
# <RCC><qresource ...>
set(qrcContents "<RCC>\n <qresource")
string(APPEND qrcContents " prefix=\"${rcc_PREFIX}\"")
_qt_internal_escape_xml_characters("${rcc_PREFIX}" escaped_rcc_PREFIX SUBSET [[&<"]])
string(APPEND qrcContents " prefix=\"${escaped_rcc_PREFIX}\"")
# we assume that a valid language can't contain a character which needs
# XML escaping
if (rcc_LANG)
string(APPEND qrcContents " lang=\"${rcc_LANG}\"")
endif()
@ -2331,13 +2376,24 @@ function(_qt_internal_process_resource target resourceName)
get_property(is_empty SOURCE ${file} PROPERTY QT_DISCARD_FILE_CONTENTS)
### FIXME: escape file paths to be XML conform
# <file ...>...</file>
string(APPEND qrcContents " <file alias=\"${file_resource_path}\"")
# We need XML escaping; alias is a quote enclosed attribute, file is text
_qt_internal_escape_xml_characters(
"${file_resource_path}"
escaped_file_resource_path
SUBSET [[&<"]]
)
_qt_internal_escape_xml_characters(
"${file}"
escaped_file
SUBSET [[&<]]
)
string(APPEND qrcContents " <file alias=\"${escaped_file_resource_path}\"")
if(is_empty)
string(APPEND qrcContents " empty=\"true\"")
endif()
string(APPEND qrcContents ">${file}</file>\n")
string(APPEND qrcContents ">${escaped_file}</file>\n")
list(APPEND files "${file}")
set(scope_args)

View File

@ -0,0 +1 @@
This is a test

View File

@ -22,4 +22,17 @@ qt_add_resources(test_add_resource_prefix "resources_with_prefix"
PREFIX "/resources"
FILES resource_file.txt)
# Test that we handle prefixes with XML meta-character's.
# Also use the opportunity to cover testing of the file path itself
# and of aliases, too
set_source_files_properties([[&'.txt]]
PROPERTIES QT_RESOURCE_ALIAS [[&"'<>.alias]]
)
qt_add_resources(test_add_resource_prefix "resources_with_tricky_xml"
PREFIX [[/&"'<>]]
ALIAS [[&"'<>.alias]]
FILES [[&'.txt]])
target_link_libraries(test_add_resource_prefix PRIVATE Qt::Core Qt::Test)

View File

@ -10,6 +10,7 @@ class TestAddResourcePrefix : public QObject
private slots:
void resourceInDefaultPathExists();
void resourceInGivenPathExists();
void xmlEscaping();
};
void TestAddResourcePrefix::resourceInDefaultPathExists()
@ -22,5 +23,11 @@ void TestAddResourcePrefix::resourceInGivenPathExists()
QVERIFY(QFile::exists(":/resources/resource_file.txt"));
}
void TestAddResourcePrefix::xmlEscaping()
{
QVERIFY(QFile::exists(":/&\"'<>/&\"'<>.alias"));
}
QTEST_MAIN(TestAddResourcePrefix)
#include "main.moc"