Vendor import of expat 2.10.0

This commit is contained in:
Philip Paeps committed 2026-11-02 23:10:14 +08:00
1 parent 91970165e3
commit 27ac437388
34 files changed
+1641 -192

No files matched your search

+89
View File
@@ -21,6 +21,86 @@
// Sebastian Pipping -- Berlin, 2026-09-22 //
///////////////////////////////////////////////////////////////////////////////
Release 2.10.0 Mon November 2 2026
Security fixes:
#1431 CVE-2026-113351 -- Fix denial of service with use of
`XML_ParserReset`
#1433 #1436 ..
#1438 #1439 ..
#1442 CVE-2026-115506 -- Detect billion laughs attacks quicker
and more of its variations. Related payload fuzzer at:
https://github.com/hannob/lololol
Please note that a layer of compression around XML can
significantly reduce the minimum attack payload size.
In case of backporting, please note pull request #1439
in particular.
#1444 CVE-2026-117610 -- Fix initial non-final empty subparser
parse calls. Was segfaulting previously.
#1450 CVE-2026-118370 -- Protect `attIndex` and `nPrefixes` in
function `storeAtts` from signed integer overflow
Bug fixes:
#1452 lib: Initialize `m_parsingStatus.finalBuffer` in `parserInit`
to fix calling `XML_GetParsingStatus` right after
parser creation
#1457 #1459 lib: Improve `XML_Parse` for `XML_CONTEXT_BYTES == 0`
including NULL pointer arithmetic undefined behavior
and uninitialized variable access
#1458 lib: Use C99 length modifiers for `ptrdiff_t` and `size_t`
to fix support for CHERI-RISC-V
#1461 lib: Check `isCalledFromInsideHandler` first in `XML_Parse`
#1467 lib: Add missing check for a character handler reset
Other changes:
#1441 Autotools: Move `-lm` from LDFLAGS to LIBADD/LDADD
to better support compilation with `-Wl,--as-needed`
#1412 CMake: Support repeated `find_package` calls
#1413 lib: Address warning `XML_GE is not defined [-Werror=undef]`
#1415 lib: Stop delaying `callStoreEntityValue` error handling
#1415 lib: Add missing call to `poolDiscard`
#1437 lib: Add missing `NULL` check for `m_startDoctypeDeclHandler`
#1417 lib: Migrate `ENTITY.textLen` and `ENTITY.processed` from
`int` to `size_t`
#1463 lib: Fix integer truncation in `XmlGetAttributes`/`getAtts`
#1420 lib: Some more `XML_Bool` to `bool` migration
#1449 #1460 lib: Document the memory reuse black magic that function
`storeAtts` is doing
#1453 lib: Document failure to reject bad redefinitions of
predefined entities
#1432 tests: Replace `XML_SetHashSalt` with
`XML_SetHashSalt16Bytes`
#1416 #1435 Version info bumped from 14:0:13 (libexpat*.so.1.13.0)
to 14:1:13 (libexpat*.so.1.13.1); see https://verbump.de/
for what these numbers do
Infrastructure:
#1414 CI: Stop using about-to-be-removed image "macos-14"
#1418 #1434 CI: Bump MinGW Clang from 23.1.2 to 23.1.4
#1443 CI: Bump Fil-C from 0.686 to 0.687
#1456 Drop a `lib/xcsinc.c` leftover
Special thanks to:
Alexander Bluhm
Bartłomiej Dmitruk
Berkay Eren Ürün
Brooks Davis
brspoon
Funda Wang
Hanno Böck
Matthew Fernandez
Minkyung Park
Mirko Swillus
Pu Cheng
Scott Moore
Shani Yosef
Xisco Fauli
and
City of Munich Open Source Sabbatical
echo.ai
Siemens
Striga
VulnCheck
Release 2.9.0 Mon October 5 2026
Security fixes:
#1392 CVE-2026-102633 -- Integer overflow in function
@@ -106,6 +186,7 @@ Release 2.9.0 Mon October 5 2026
Wade Sparks III
and
City of Munich Open Source Sabbatical
Siemens
VulnCheck
Release 2.8.5 Tue September 22 2026
@@ -239,6 +320,7 @@ Release 2.8.4 Mon August 31 2026
and
City of Munich Open Source Sabbatical
Moonshot AI
Siemens
VulnCheck
Z.ai
@@ -425,6 +507,8 @@ Release 2.8.1 Sun May 10 2026
Matthew Fernandez
Nick Wellnhofer
Tania Somanna
and
Siemens
Release 2.8.0 Fri April 24 2026
Security fixes:
@@ -537,6 +621,7 @@ Release 2.7.5 Tue March 17 2026
AddressSanitizer
Buttercup
OSS-Fuzz / ClusterFuzz
Siemens
Trail of Bits
Release 2.7.4 Sat January 31 2026
@@ -678,6 +763,7 @@ Release 2.7.3 Wed September 24 2025
and
Clang/GCC UndefinedBehaviorSanitizer
OSS-Fuzz / ClusterFuzz
Siemens
Z3 Theorem Prover
Release 2.7.2 Tue September 16 2025
@@ -791,6 +877,7 @@ Release 2.7.1 Thu March 27 2025
Matthew Fernandez
and
Perl XML::Parser
Siemens
Release 2.7.0 Thu March 13 2025
Security fixes:
@@ -950,6 +1037,8 @@ Release 2.6.3 Wed September 4 2024
Dag-Erling Smørgrav
Ferenc Géczi
TaiYou
and
Siemens
Release 2.6.2 Wed March 13 2024
Security fixes:
+1 -1
View File
@@ -17,7 +17,7 @@
> Thank you! :heart: :pray:
# Expat, Release 2.9.0
# Expat, Release 2.10.0
This is Expat, a C99 library for parsing
[XML 1.0 Fourth Edition](https://www.w3.org/TR/2006/REC-xml-20060816/), started by
+1 -1
View File
@@ -104,7 +104,7 @@ dnl If the API changes incompatibly set LIBAGE back to 0
dnl
LIBCURRENT=14 # sync
LIBREVISION=0 # with
LIBREVISION=1 # with
LIBAGE=13 # CMakeLists.txt!
AC_CONFIG_HEADERS([expat_config.h])
+7 -1
View File
@@ -53,7 +53,7 @@
<body>
<div>
<h1>
The Expat XML Parser <small>Release 2.9.0</small>
The Expat XML Parser <small>Release 2.10.0</small>
</h1>
</div>
@@ -2862,6 +2862,12 @@ typedef void
The <code>notationName</code> argument will have a non-<code>NULL</code> value
only for unparsed entity declarations.
</p>
<p>
<strong>Note:</strong> Length <code>value_length</code> can wrap around on
platforms where <code>int</code> is smaller than <code>size_t</code> including
<code>x86_64</code>.
</p>
</div>
<div class="handler">
+1 -1
View File
@@ -5,7 +5,7 @@
\\$2 \(la\\$1\(ra\\$3
..
.if \n(.g .mso www.tmac
.TH XMLWF 1 "October 5, 2026" "" ""
.TH XMLWF 1 "November 2, 2026" "" ""
.SH NAME
xmlwf \- Determines if an XML document is well-formed
.SH SYNOPSIS
+1 -1
View File
@@ -21,7 +21,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
<!ENTITY dhfirstname "<firstname>Scott</firstname>">
<!ENTITY dhsurname "<surname>Bronson</surname>">
<!ENTITY dhdate "<date>October 5, 2026</date>">
<!ENTITY dhdate "<date>November 2, 2026</date>">
<!-- Please adjust this^^ date whenever cutting a new release. -->
<!ENTITY dhsection "<manvolnum>1</manvolnum>">
<!ENTITY dhemail "<email>[email protected]</email>">
+4 -1
View File
@@ -12,6 +12,7 @@
# Copyright (c) 2026 Gordon Messmer <[email protected]>
# Copyright (c) 2026 Nick Begg <[email protected]>
# Copyright (c) 2026 Matthew Fernandez <[email protected]>
# Copyright (c) 2026 Funda Wang <[email protected]>
# Licensed under the MIT license:
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -45,13 +46,14 @@ endif
libexpat_la_LDFLAGS = \
@AM_LDFLAGS@ \
@LIBM@ \
-no-undefined \
-version-info @LIBCURRENT@:@LIBREVISION@:@LIBAGE@
if HAVE_VSCRIPT
libexpat_la_LDFLAGS += $(VSCRIPT_LDFLAGS),@builddir@/libexpat.map
endif
libexpat_la_LIBADD = @LIBM@
libexpat_la_SOURCES = \
xcs.c \
xmlparse.c \
@@ -113,6 +115,7 @@ uninstall-local:
EXTRA_DIST = \
ascii.h \
asciitab.h \
entities.h \
expat_external.h \
expat.h \
fallthrough.h \
+5 -2
View File
@@ -28,6 +28,7 @@
# Copyright (c) 2026 Gordon Messmer <[email protected]>
# Copyright (c) 2026 Nick Begg <[email protected]>
# Copyright (c) 2026 Matthew Fernandez <[email protected]>
# Copyright (c) 2026 Funda Wang <[email protected]>
# Licensed under the MIT license:
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -205,7 +206,7 @@ am__uninstall_files_from_dir = { \
am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(docdir)" \
"$(DESTDIR)$(includedir)"
LTLIBRARIES = $(lib_LTLIBRARIES) $(noinst_LTLIBRARIES)
libexpat_la_LIBADD =
libexpat_la_DEPENDENCIES =
am__libexpat_la_SOURCES_DIST = xcs.c xmlparse.c xmltok.c xmlrole.c \
random_arc4random.c random_arc4random.h \
random_arc4random_buf.c random_arc4random_buf.h \
@@ -493,8 +494,9 @@ include_HEADERS = \
lib_LTLIBRARIES = libexpat.la
@WITH_TESTS_TRUE@noinst_LTLIBRARIES = libtestpat.la
libexpat_la_LDFLAGS = @AM_LDFLAGS@ @LIBM@ -no-undefined -version-info \
libexpat_la_LDFLAGS = @AM_LDFLAGS@ -no-undefined -version-info \
@LIBCURRENT@:@LIBREVISION@:@LIBAGE@ $(am__append_1)
libexpat_la_LIBADD = @LIBM@
libexpat_la_SOURCES = xcs.c xmlparse.c xmltok.c xmlrole.c \
$(am__append_2) $(am__append_3) $(am__append_4) \
$(am__append_5) $(am__append_6) $(am__append_7)
@@ -507,6 +509,7 @@ doc_DATA = \
EXTRA_DIST = \
ascii.h \
asciitab.h \
entities.h \
expat_external.h \
expat.h \
fallthrough.h \
+2 -1
View File
@@ -10,7 +10,7 @@
Copyright (c) 2000 Clark Cooper <[email protected]>
Copyright (c) 2002 Fred L. Drake, Jr. <[email protected]>
Copyright (c) 2007 Karl Waclawek <[email protected]>
Copyright (c) 2017 Sebastian Pipping <[email protected]>
Copyright (c) 2017-2026 Sebastian Pipping <[email protected]>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
@@ -113,6 +113,7 @@
#define ASCII_LT 0x3C
#define ASCII_EQUALS 0x3D
#define ASCII_GT 0x3E
#define ASCII_QUEST 0x3F
#define ASCII_LSQB 0x5B
#define ASCII_RSQB 0x5D
#define ASCII_UNDERSCORE 0x5F
+99
View File
@@ -0,0 +1,99 @@
/* Internal helpers related to entities
__ __ _
___\ \/ /_ __ __ _| |_
/ _ \\ /| '_ \ / _` | __|
| __// \| |_) | (_| | |_
\___/_/\_\ .__/ \__,_|\__|
|_| XML parser
Copyright (c) 2026 Sebastian Pipping <[email protected]>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
SPDX-License-Identifier: MIT
*/
#if ! defined(EXPAT_ENTITIES_H)
# define EXPAT_ENTITIES_H 1
# include "expat_config.h" // for XML_GE
# include "expat_external.h" // for XML_Char
# include "internal.h" // for XML_NONTESTING_STATIC
# include <stdbool.h> // for size_t
# include <stddef.h> // for size_t
typedef struct {
const XML_Char *name;
const XML_Char *textPtr;
size_t textLen; /* length in XML_Chars */
size_t processed; /* # of processed bytes - when suspended */
const XML_Char *systemId;
const XML_Char *base;
const XML_Char *publicId;
const XML_Char *notation;
bool open;
bool hasMore; /* true if entity has not been completely processed */
/* An entity can be open while being already completely processed (!hasMore).
The reason is the delayed closing of entities until their inner
entities are processed and closed */
bool is_param;
bool is_internal; /* true if declared in internal subset outside PE */
# if XML_GE == 1
/* Indirect accounting bytes charged when this entity is expanded: 1 per text
character, plus each reference's "&name;"/"%name;" token length and its own
expansion. Matches XML_ACCOUNT_ENTITY_EXPANSION, so it is not the
replacement-text size. 0 means "not computed yet" (an empty entity is 0
too, told apart by textLen == 0). */
XmlBigCount expandedSize;
bool beingAnalyzed; /* on the resolveEntityClosureExpandedSize depth-first
search stack; used for cycle detection */
const XML_Char
*scanPtr; /* cursor position into the entity's replacement text; NULL when
`textPtr` is NULL, else a pointer into the entity value,
ranging from `.textPtr` to `.textPtr + .textLen`. */
# endif /* XML_GE == 1 */
} ENTITY;
# if XML_GE == 1
/* How a scanEntityText call ended. */
enum EntityScanResult {
ENTITY_SCAN_COMPLETE, /* reached end of text; *expandedSize is final */
ENTITY_SCAN_BLOCKED, /* stopped at a defined entity whose expanded
size has not been computed yet; *blockerOut
names the entity whose size to compute
first */
ENTITY_SCAN_OVERFLOW /* the expanded size would exceed XmlBigCount;
callers must abort parsing */
};
XML_NONTESTING_STATIC int isCDataCommentOrPIStart(const XML_Char *pos,
size_t maxLen,
size_t *openTextLen);
XML_NONTESTING_STATIC int isPredefinedEntityName(const XML_Char *name,
size_t nameLen);
XML_NONTESTING_STATIC enum EntityScanResult
scanEntityText(XML_Parser parser, ENTITY *entity, const XML_Char **posPtr,
XmlBigCount *expandedSize, ENTITY **blockerOut);
# endif /* XML_GE == 1 */
#endif // ! defined(EXPAT_ENTITIES_H)
+3 -2
View File
@@ -21,6 +21,7 @@
Copyright (c) 2024 Taichi Haradaguchi <[email protected]>
Copyright (c) 2025 Matthew Fernandez <[email protected]>
Copyright (c) 2026 Braian Plaku <[email protected]>
Copyright (c) 2026 Xisco Fauli <[email protected]>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
@@ -1084,7 +1085,7 @@ enum XML_Parser_Property {
# if defined(XML_TESTING)
XML_PROP_INVALID = 0,
# endif
# if XML_GE == 1
# if defined(XML_GE) && XML_GE == 1
XML_PROP_ALLOC_TRACKER_ACTIVATION_THRESHOLD = 1, // of type `uint64_t`
XML_PROP_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION = 2, // of type `double`
XML_PROP_BILLION_LAUGHS_ACTIVATION_THRESHOLD = 3, // of type `uint64_t`
@@ -1146,7 +1147,7 @@ XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled);
See https://semver.org
*/
# define XML_MAJOR_VERSION 2
# define XML_MINOR_VERSION 9
# define XML_MINOR_VERSION 10
# define XML_MICRO_VERSION 0
# ifdef __cplusplus
+1 -1
View File
@@ -12,7 +12,7 @@
Copyright (c) 2001-2002 Greg Stein <[email protected]>
Copyright (c) 2002-2006 Karl Waclawek <[email protected]>
Copyright (c) 2016 Cristian Rodríguez <[email protected]>
Copyright (c) 2016-2025 Sebastian Pipping <[email protected]>
Copyright (c) 2016-2026 Sebastian Pipping <[email protected]>
Copyright (c) 2017 Rhodri James <[email protected]>
Copyright (c) 2018 Yury Gribov <[email protected]>
Copyright (c) 2026 Matthew Fernandez <[email protected]>
+5 -3
View File
@@ -29,6 +29,7 @@
Copyright (c) 2026 Matthew Wozniczka <[email protected]>
Copyright (c) 2026 Braian Plaku <[email protected]>
Copyright (c) 2026 Florian Schmaus <[email protected]>
Copyright (c) 2026 Brooks Davis <[email protected]>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
@@ -90,11 +91,10 @@
# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"
# endif
# else
# include <inttypes.h> // PRIdPTR, PRIuPTR
# define EXPAT_FMT_LLX(midpart) "%" midpart "llx"
# define EXPAT_FMT_ULL(midpart) "%" midpart "llu"
# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart PRIdPTR
# define EXPAT_FMT_SIZE_T(midpart) "%" midpart PRIuPTR
# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "td"
# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "zu"
# endif
# ifndef UNUSED_P
@@ -127,6 +127,8 @@ union expat_align {
# include "expat.h" // so we can use type XML_Parser below
typedef unsigned long long XmlBigCount;
void _INTERNAL_trim_to_complete_utf8_characters(const char *from,
const char **fromLimRef);
+21
View File
@@ -88,3 +88,24 @@ xcsncmp(const XML_Char *a, const XML_Char *b, size_t len) {
return strncmp(a, b, len);
#endif
}
// A port of `strnstr(3bsd)` to `XML_Char`
const XML_Char *
xcsnstr(const XML_Char *haystack, const XML_Char *needle, size_t maxLen) {
// NOTE: We are not calling `xcsnlen(haystack, maxLen)` because
// `haystack` could be long and `xcsnlen` could be used in a loop
// and we need to avoid quadratic runtime behavior.
if (! *needle)
return haystack; // i.e. empty needle is always a match
const size_t needleLen = xcslen(needle);
if (needleLen > maxLen)
return NULL;
const size_t maxTries = (maxLen - needleLen) + 1;
for (size_t i = 0; i < maxTries && haystack[i]; i++) {
if (haystack[i] == needle[0]
&& xcsncmp(haystack + i, needle, needleLen) == 0) {
return haystack + i;
}
}
return NULL;
}
+3
View File
@@ -41,4 +41,7 @@ extern size_t xcslen(const XML_Char *s);
extern int xcscmp(const XML_Char *a, const XML_Char *b);
extern int xcsncmp(const XML_Char *a, const XML_Char *b, size_t len);
extern const XML_Char *xcsnstr(const XML_Char *haystack, const XML_Char *needle,
size_t maxLen);
#endif // ! defined(EXPAT_XCS_H)
+731 -138
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -176,8 +176,8 @@ struct encoding {
const char *);
int (*nameLength)(const ENCODING *, const char *);
const char *(*skipS)(const ENCODING *, const char *);
int (*getAtts)(const ENCODING *enc, const char *ptr, int attsMax,
ATTRIBUTE *atts);
size_t (*getAtts)(const ENCODING *enc, const char *ptr, size_t attsMax,
ATTRIBUTE *atts);
int (*charRefNumber)(const ENCODING *enc, const char *ptr);
int (*predefinedEntityName)(const ENCODING *, const char *, const char *);
void (*updatePosition)(const ENCODING *, const char *ptr, const char *end,
+3 -3
View File
@@ -1505,11 +1505,11 @@ PREFIX(isPublicId)(const ENCODING *enc, const char *ptr, const char *end,
first attsMax attributes are stored in atts.
*/
static int
PREFIX(getAtts)(const ENCODING *enc, const char *ptr, int attsMax,
static size_t
PREFIX(getAtts)(const ENCODING *enc, const char *ptr, size_t attsMax,
ATTRIBUTE *atts) {
enum { other, inName, inValue } state = inName;
int nAtts = 0;
size_t nAtts = 0;
int open = 0; /* defined when state == inValue;
initialization just to shut up compilers */
+5 -2
View File
@@ -10,6 +10,7 @@
# Copyright (c) 2017-2022 Rhodri James <[email protected]>
# Copyright (c) 2020 Jeffrey Walton <[email protected]>
# Copyright (c) 2024 Dag-Erling Smørgrav <[email protected]>
# Copyright (c) 2026 Funda Wang <[email protected]>
# Licensed under the MIT license:
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -50,6 +51,7 @@ runtests_SOURCES = \
dummy.c \
handlers.c \
hash_tests.c \
entities_tests.c \
memcheck.c \
minicheck.c \
misc_tests.c \
@@ -60,9 +62,9 @@ runtests_SOURCES = \
structdata.c \
xcs_tests.c
runtests_LDADD = ../lib/libtestpat.la
runtests_LDADD = ../lib/libtestpat.la @LIBM@
runtests_LDFLAGS = @AM_LDFLAGS@ @LIBM@
runtests_LDFLAGS = @AM_LDFLAGS@
EXTRA_DIST = \
acc_tests.h \
@@ -73,6 +75,7 @@ EXTRA_DIST = \
dummy.h \
handlers.h \
hash_tests.h \
entities_tests.h \
memcheck.h \
minicheck.h \
misc_tests.h \
+19 -12
View File
@@ -26,6 +26,7 @@
# Copyright (c) 2017-2022 Rhodri James <[email protected]>
# Copyright (c) 2020 Jeffrey Walton <[email protected]>
# Copyright (c) 2024 Dag-Erling Smørgrav <[email protected]>
# Copyright (c) 2026 Funda Wang <[email protected]>
# Licensed under the MIT license:
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -150,10 +151,10 @@ CONFIG_CLEAN_VPATH_FILES =
am_runtests_OBJECTS = acc_tests.$(OBJEXT) alloc_tests.$(OBJEXT) \
basic_tests.$(OBJEXT) chardata.$(OBJEXT) common.$(OBJEXT) \
dummy.$(OBJEXT) handlers.$(OBJEXT) hash_tests.$(OBJEXT) \
memcheck.$(OBJEXT) minicheck.$(OBJEXT) misc_tests.$(OBJEXT) \
ns_tests.$(OBJEXT) nsalloc_tests.$(OBJEXT) \
props_tests.$(OBJEXT) runtests.$(OBJEXT) structdata.$(OBJEXT) \
xcs_tests.$(OBJEXT)
entities_tests.$(OBJEXT) memcheck.$(OBJEXT) \
minicheck.$(OBJEXT) misc_tests.$(OBJEXT) ns_tests.$(OBJEXT) \
nsalloc_tests.$(OBJEXT) props_tests.$(OBJEXT) \
runtests.$(OBJEXT) structdata.$(OBJEXT) xcs_tests.$(OBJEXT)
runtests_OBJECTS = $(am_runtests_OBJECTS)
runtests_DEPENDENCIES = ../lib/libtestpat.la
AM_V_lt = $(am__v_lt_@AM_V@)
@@ -181,12 +182,13 @@ am__maybe_remake_depfiles = depfiles
am__depfiles_remade = ./$(DEPDIR)/acc_tests.Po \
./$(DEPDIR)/alloc_tests.Po ./$(DEPDIR)/basic_tests.Po \
./$(DEPDIR)/chardata.Po ./$(DEPDIR)/common.Po \
./$(DEPDIR)/dummy.Po ./$(DEPDIR)/handlers.Po \
./$(DEPDIR)/hash_tests.Po ./$(DEPDIR)/memcheck.Po \
./$(DEPDIR)/minicheck.Po ./$(DEPDIR)/misc_tests.Po \
./$(DEPDIR)/ns_tests.Po ./$(DEPDIR)/nsalloc_tests.Po \
./$(DEPDIR)/props_tests.Po ./$(DEPDIR)/runtests.Po \
./$(DEPDIR)/structdata.Po ./$(DEPDIR)/xcs_tests.Po
./$(DEPDIR)/dummy.Po ./$(DEPDIR)/entities_tests.Po \
./$(DEPDIR)/handlers.Po ./$(DEPDIR)/hash_tests.Po \
./$(DEPDIR)/memcheck.Po ./$(DEPDIR)/minicheck.Po \
./$(DEPDIR)/misc_tests.Po ./$(DEPDIR)/ns_tests.Po \
./$(DEPDIR)/nsalloc_tests.Po ./$(DEPDIR)/props_tests.Po \
./$(DEPDIR)/runtests.Po ./$(DEPDIR)/structdata.Po \
./$(DEPDIR)/xcs_tests.Po
am__mv = mv -f
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
@@ -650,6 +652,7 @@ runtests_SOURCES = \
dummy.c \
handlers.c \
hash_tests.c \
entities_tests.c \
memcheck.c \
minicheck.c \
misc_tests.c \
@@ -660,8 +663,8 @@ runtests_SOURCES = \
structdata.c \
xcs_tests.c
runtests_LDADD = ../lib/libtestpat.la
runtests_LDFLAGS = @AM_LDFLAGS@ @LIBM@
runtests_LDADD = ../lib/libtestpat.la @LIBM@
runtests_LDFLAGS = @AM_LDFLAGS@
EXTRA_DIST = \
acc_tests.h \
alloc_tests.h \
@@ -671,6 +674,7 @@ EXTRA_DIST = \
dummy.h \
handlers.h \
hash_tests.h \
entities_tests.h \
memcheck.h \
minicheck.h \
misc_tests.h \
@@ -738,6 +742,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chardata.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/common.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dummy.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/entities_tests.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handlers.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash_tests.Po@am__quote@ # am--include-marker
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memcheck.Po@am__quote@ # am--include-marker
@@ -1184,6 +1189,7 @@ distclean: distclean-recursive
-rm -f ./$(DEPDIR)/chardata.Po
-rm -f ./$(DEPDIR)/common.Po
-rm -f ./$(DEPDIR)/dummy.Po
-rm -f ./$(DEPDIR)/entities_tests.Po
-rm -f ./$(DEPDIR)/handlers.Po
-rm -f ./$(DEPDIR)/hash_tests.Po
-rm -f ./$(DEPDIR)/memcheck.Po
@@ -1246,6 +1252,7 @@ maintainer-clean: maintainer-clean-recursive
-rm -f ./$(DEPDIR)/chardata.Po
-rm -f ./$(DEPDIR)/common.Po
-rm -f ./$(DEPDIR)/dummy.Po
-rm -f ./$(DEPDIR)/entities_tests.Po
-rm -f ./$(DEPDIR)/handlers.Po
-rm -f ./$(DEPDIR)/hash_tests.Po
-rm -f ./$(DEPDIR)/memcheck.Po
+32
View File
@@ -475,6 +475,37 @@ START_TEST(test_amplification_isolated_external_parser) {
}
END_TEST
START_TEST(test_amplification_reset_neutrality) {
// Speed up the test, chunk size expected to not matter in this test
if (g_chunkSize != 0)
return;
const char *const doc
= "<!DOCTYPE r [<!ENTITY e 'general entity'>]><r>&e;</r>";
XML_Parser parser = XML_ParserCreate(NULL);
assert_true(XML_SetPropertyDouble(
parser, XML_PROP_BILLION_LAUGHS_MAXIMUM_AMPLIFICATION, 1.0)
== XML_PROP_ERROR_NONE);
assert_true(XML_SetPropertyUInt64(
parser, XML_PROP_BILLION_LAUGHS_ACTIVATION_THRESHOLD, 0)
== XML_PROP_ERROR_NONE);
assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
/*isFinal=*/XML_TRUE)
== XML_STATUS_ERROR);
assert_true(XML_GetErrorCode(parser) == XML_ERROR_AMPLIFICATION_LIMIT_BREACH);
assert_true(XML_ParserReset(parser, NULL) == XML_STATUS_OK);
assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
/*isFinal=*/XML_TRUE)
== XML_STATUS_ERROR);
assert_true(XML_GetErrorCode(parser) == XML_ERROR_AMPLIFICATION_LIMIT_BREACH);
XML_ParserFree(parser);
}
END_TEST
#endif // XML_GE == 1
void
@@ -491,6 +522,7 @@ make_accounting_test_case(Suite *s) {
tcase_add_test(tc_accounting, test_helper_unsigned_char_to_printable);
tcase_add_test__ifdef_xml_dtd(tc_accounting,
test_amplification_isolated_external_parser);
tcase_add_test(tc_accounting, test_amplification_reset_neutrality);
#else
UNUSED_P(s);
#endif /* XML_GE == 1 */
+42
View File
@@ -2330,6 +2330,47 @@ START_TEST(test_alloc_tracker_api_float_round_trip) {
}
END_TEST
START_TEST(test_alloc_tracker_reset_neutrality) {
// Speed up the test, chunk size expected to not matter in this test
if (g_chunkSize != 0)
return;
XML_Parser parser = XML_ParserCreate(NULL);
const char *const small = "<hello/>";
// Create a document that drives `parser->m_alloc_tracker.bytesAllocated`
// towards the activation threshold
#if XML_GE == 1
const size_t bigSizeBytes
= (size_t)(1.1f * EXPAT_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT);
const char *const before = "<doc a='";
const char *const after = "'/>";
char *const big = malloc(bigSizeBytes);
assert_true(big != NULL);
memset(big, 'X', bigSizeBytes);
memcpy(big, before, strlen(before)); // without zero terminator
strcpy(big + (bigSizeBytes - strlen(after) - 1),
after); // with zero terminator
assert_true(strlen(big) == bigSizeBytes - 1); // self-test
assert_true(
_XML_Parse_SINGLE_BYTES(parser, big, strlen(big), /*isFinal=*/XML_TRUE)
== XML_STATUS_OK);
free(big);
#endif // XML_GE == 1
XML_ParserReset(parser, NULL);
assert_true(_XML_Parse_SINGLE_BYTES(parser, small, strlen(small),
/*isFinal=*/XML_TRUE)
== XML_STATUS_OK);
XML_ParserFree(parser);
}
END_TEST
START_TEST(test_mem_api_cycle) {
XML_Parser parser = XML_ParserCreate(NULL);
@@ -2445,6 +2486,7 @@ make_alloc_test_case(Suite *s) {
tcase_add_test__if_xml_ge(tc_alloc, test_alloc_tracker_getbuffer_unlimited);
tcase_add_test__if_xml_ge(tc_alloc, test_alloc_tracker_api);
tcase_add_test__if_xml_ge(tc_alloc, test_alloc_tracker_api_float_round_trip);
tcase_add_test__if_xml_ge(tc_alloc, test_alloc_tracker_reset_neutrality);
tcase_add_test(tc_alloc, test_mem_api_cycle);
tcase_add_test__if_xml_ge(tc_alloc, test_mem_api_unlimited);
+48 -15
View File
@@ -27,6 +27,7 @@
Copyright (c) 2026 Afonso Januário <afonso-januario@hotmail.com>
Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com>
Copyright (c) 2026 Filippo Tedeschi <filippotedeschi98@gmail.com>
Copyright (c) 2026 Alexander Bluhm <alexander.bluhm@gmx.net>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
@@ -208,7 +209,8 @@ START_TEST(test_hash_collision) {
* tests invoked from qa.sh usually provide a hash collision, but
* not always. This is an attempt to provide insurance.
*/
#define COLLIDING_HASH_SALT (unsigned long)SIP_ULL(0xffffffffU, 0xff99fc90U)
const uint8_t entropy[16] = {0, 0, 0, 0, 0, 0, 0, 0,
0x90, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff, 0xff};
const char *text
= "<doc>\n"
"<a1/><a2/><a3/><a4/><a5/><a6/><a7/><a8/>\n"
@@ -219,13 +221,12 @@ START_TEST(test_hash_collision) {
"<d8>This triggers the table growth and collides with b2</d8>\n"
"</doc>\n";
xmlSetHashSalt(g_parser, COLLIDING_HASH_SALT);
XML_SetHashSalt16Bytes(g_parser, entropy);
if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE)
== XML_STATUS_ERROR)
xml_failure(g_parser);
}
END_TEST
#undef COLLIDING_HASH_SALT
START_TEST(test_hash_salt_setter) {
const uint8_t entropy[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
@@ -251,6 +252,29 @@ START_TEST(test_hash_salt_setter) {
}
END_TEST
START_TEST(test_hash_salt_ulong) {
const unsigned long salt = 0x12345678;
XML_Parser parser = XML_ParserCreate(NULL);
// NULL parser should be rejected
assert_true(xmlSetHashSalt(NULL, salt) == 0);
// salt 0 is ignored, but accepted
assert_true(xmlSetHashSalt(parser, 0) == 1);
// Setting should be allowed more than once
assert_true(xmlSetHashSalt(parser, salt) == 1);
assert_true(xmlSetHashSalt(parser, salt) == 1);
// But not after parsing has started
assert_true(XML_Parse(parser, "", 0, XML_FALSE /* isFinal */)
== XML_STATUS_OK);
assert_true(xmlSetHashSalt(parser, salt) == 0);
XML_ParserFree(parser);
}
END_TEST
/* Regression test for SF bug #491986. */
START_TEST(test_danish_latin1) {
const char *text = "<?xml version='1.0' encoding='iso-8859-1'?>\n"
@@ -2743,12 +2767,13 @@ END_TEST
/* Test foreign DTD handling */
START_TEST(test_set_foreign_dtd) {
const uint8_t entropy[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8};
const char *text1 = "<?xml version='1.0' encoding='us-ascii'?>\n";
const char *text2 = "<doc>&entity;</doc>";
ExtTest test_data = {"<!ELEMENT doc (#PCDATA)*>", NULL, NULL};
/* Check hash salt is passed through too */
xmlSetHashSalt(g_parser, 0x12345678);
XML_SetHashSalt16Bytes(g_parser, entropy);
XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
XML_SetUserData(g_parser, &test_data);
XML_SetExternalEntityRefHandler(g_parser, external_entity_loader);
@@ -2767,7 +2792,7 @@ START_TEST(test_set_foreign_dtd) {
!= XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING)
fail("Failed to reject late foreign DTD setting");
/* Ditto for the hash salt */
if (xmlSetHashSalt(g_parser, 0x23456789))
if (XML_SetHashSalt16Bytes(g_parser, entropy))
fail("Failed to reject late hash salt change");
/* Now finish the parse */
@@ -2812,13 +2837,14 @@ END_TEST
/* Test foreign DTD use with a doctype */
START_TEST(test_foreign_dtd_with_doctype) {
const uint8_t entropy[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8};
const char *text1 = "<?xml version='1.0' encoding='us-ascii'?>\n"
"<!DOCTYPE doc [<!ENTITY entity 'hello world'>]>\n";
const char *text2 = "<doc>&entity;</doc>";
ExtTest test_data = {"<!ELEMENT doc (#PCDATA)*>", NULL, NULL};
/* Check hash salt is passed through too */
xmlSetHashSalt(g_parser, 0x12345678);
XML_SetHashSalt16Bytes(g_parser, entropy);
XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
XML_SetUserData(g_parser, &test_data);
XML_SetExternalEntityRefHandler(g_parser, external_entity_loader);
@@ -2837,7 +2863,7 @@ START_TEST(test_foreign_dtd_with_doctype) {
!= XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING)
fail("Failed to reject late foreign DTD setting");
/* Ditto for the hash salt */
if (xmlSetHashSalt(g_parser, 0x23456789))
if (XML_SetHashSalt16Bytes(g_parser, entropy))
fail("Failed to reject late hash salt change");
/* Now finish the parse */
@@ -4402,17 +4428,23 @@ START_TEST(test_attribute_enum_value) {
}
END_TEST
/* Slightly bizarrely, the library seems to silently ignore entity
* definitions for predefined entities, even when they are wrong. The
* language of the XML 1.0 spec is somewhat unhelpful as to what ought
* to happen, so this is currently treated as acceptable.
*/
START_TEST(test_predefined_entity_redefinition) {
// NOTE: This violates XML 1.0 fourth edition section
// "4.6 Predefined Entities" (that demands rejection of bad
// redefinitions) only for backwards-compatibility reasons.
const char *text = "<!DOCTYPE doc [\n"
"<!ENTITY apos 'foo'>\n"
" <!ENTITY amp 'bad'>\n"
" <!ENTITY apos 'bad'>\n"
" <!ENTITY gt 'bad'>\n"
" <!ENTITY lt 'bad'>\n"
" <!ENTITY quot 'bad'>\n"
"]>\n"
"<doc>&apos;</doc>";
run_character_check(text, XCS("'"));
"<doc>&amp;&apos;&gt;&lt;&quot;</doc>";
run_character_check(text, XCS("&"
"'"
">"
"<"
"\""));
}
END_TEST
@@ -7154,6 +7186,7 @@ make_basic_test_case(Suite *s) {
tcase_add_test(tc_basic, test_nobom_utf16_le);
tcase_add_test(tc_basic, test_hash_collision);
tcase_add_test(tc_basic, test_hash_salt_setter);
tcase_add_test(tc_basic, test_hash_salt_ulong);
tcase_add_test(tc_basic, test_illegal_utf8);
tcase_add_test(tc_basic, test_utf8_auto_align);
tcase_add_test(tc_basic, test_utf16);
+12
View File
@@ -339,3 +339,15 @@ portable_strndup(const char *s, size_t n) {
return buffer;
}
XML_Char *
xcsdup_str(const char *s) {
const size_t len = strlen(s);
XML_Char *const buf = calloc(len + /*terminator*/ 1, sizeof(XML_Char));
if (! buf)
return NULL;
XML_Char *t = buf;
while (*s)
*t++ = *s++;
return buf;
}
+2
View File
@@ -140,4 +140,6 @@ extern void *duff_reallocator(void *ptr, size_t size);
extern char *portable_strndup(const char *s, size_t n);
extern XML_Char *xcsdup_str(const char *s);
#endif /* XML_COMMON_H */
+3
View File
@@ -45,6 +45,9 @@
#ifndef XML_DUMMY_H
#define XML_DUMMY_H
#include "expat.h" // for XML_Content
#include "expat_external.h" // for XMLCALL, XML_Char
#define DUMMY_START_DOCTYPE_HANDLER_FLAG (1UL << 0)
#define DUMMY_END_DOCTYPE_HANDLER_FLAG (1UL << 1)
#define DUMMY_ENTITY_DECL_HANDLER_FLAG (1UL << 2)
+246
View File
@@ -0,0 +1,246 @@
/* Unit tests related to internal handling of entities
__ __ _
___\ \/ /_ __ __ _| |_
/ _ \\ /| '_ \ / _` | __|
| __// \| |_) | (_| | |_
\___/_/\_\ .__/ \__,_|\__|
|_| XML parser
Copyright (c) 2026 Sebastian Pipping <[email protected]>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
SPDX-License-Identifier: MIT
*/
#include "entities_tests.h"
#include "expat_config.h" // for XML_GE
#include "ascii.h" // for ASCII_*
#include "common.h" // for g_chunkSize, xcsdup_str
#include "entities.h" // for isCDataCommentOrPIStart, isPredefinedEntityName, ..
#include "xmltok.h" // for XML_TOK_*
#include "xcs.h" // for xcslen
#include <string.h> // for strlen
static void
assert_entity_expanded_size_equals(XML_Parser parser, const char *text,
XmlBigCount expectedExpandedSize);
static void assert_predefined_entity_name_match(const char *text,
int expectedCharacter);
static ENTITY *createGeneralEntityWithText(const char *text);
START_TEST(test_is_cdata_comment_or_pi_start) {
// Speed up the test, chunk size expected to not matter in this test
if (g_chunkSize != 0)
return;
XML_Char *const cdataOpen = xcsdup_str("<![CDATA[");
XML_Char *const commentOpen = xcsdup_str("<!--");
XML_Char *const piOpen = xcsdup_str("<?");
#if XML_GE == 1
const size_t expectedCdataOpenLen = xcslen(cdataOpen);
const size_t expectedCommentOpenLen = xcslen(commentOpen);
const size_t expectedPiOpenLen = xcslen(piOpen);
size_t actualCdataOpenLen = 0;
assert_true(isCDataCommentOrPIStart(cdataOpen, expectedCdataOpenLen,
&actualCdataOpenLen)
== XML_TOK_CDATA_SECT_OPEN);
assert_true(actualCdataOpenLen == expectedCdataOpenLen);
size_t actualCommentOpenLen = 0;
assert_true(isCDataCommentOrPIStart(commentOpen, expectedCommentOpenLen,
&actualCommentOpenLen)
== XML_TOK_COMMENT);
assert_true(actualCommentOpenLen == expectedCommentOpenLen);
size_t actualPiOpenLen = 0;
assert_true(
isCDataCommentOrPIStart(piOpen, expectedPiOpenLen, &actualPiOpenLen)
== XML_TOK_PI);
assert_true(actualCdataOpenLen == expectedCdataOpenLen);
size_t dummy = 0;
assert_true(isCDataCommentOrPIStart(piOpen, 0, &dummy) == XML_TOK_INVALID);
assert_true(isCDataCommentOrPIStart(piOpen, 1, &dummy) == XML_TOK_INVALID);
#endif // XML_GE == 1
free(cdataOpen);
free(commentOpen);
free(piOpen);
}
END_TEST
static void
assert_predefined_entity_name_match(const char *text, int expectedCharacter) {
XML_Char *const xcsText = xcsdup_str(text);
assert_true(xcsText != NULL);
#if XML_GE == 1
assert_true(isPredefinedEntityName(xcsText, xcslen(xcsText))
== expectedCharacter);
#else
UNUSED_P(expectedCharacter);
#endif // XML_GE == 1
free(xcsText);
}
START_TEST(test_is_predefined_entity_name) {
// Speed up the test, chunk size expected to not matter in this test
if (g_chunkSize != 0)
return;
assert_predefined_entity_name_match("amp", ASCII_AMP);
assert_predefined_entity_name_match("apos", ASCII_APOS);
assert_predefined_entity_name_match("gt", ASCII_GT);
assert_predefined_entity_name_match("lt", ASCII_LT);
assert_predefined_entity_name_match("quot", ASCII_QUOT);
}
END_TEST
static ENTITY *
createGeneralEntityWithText(const char *text) {
assert_true(text != NULL);
ENTITY *const entity = calloc(1, sizeof(ENTITY));
assert_true(entity != NULL);
entity->textPtr = xcsdup_str(text);
assert_true(entity->textPtr != NULL);
entity->textLen = xcslen(entity->textPtr);
#if XML_GE == 1
entity->scanPtr = entity->textPtr;
#endif // XML_GE == 1
return entity;
}
static void
assert_entity_expanded_size_equals(XML_Parser parser, const char *text,
XmlBigCount expectedExpandedSize) {
// Turn `text` into a general entity with that text
ENTITY *const entity = createGeneralEntityWithText(text);
#if XML_GE == 1
XmlBigCount actualExpandedSize = 0;
assert_true(scanEntityText(parser, entity, &entity->scanPtr,
&actualExpandedSize, NULL)
== ENTITY_SCAN_COMPLETE);
assert_true(entity->scanPtr == entity->textPtr + entity->textLen);
assert_true(actualExpandedSize == expectedExpandedSize);
#else
UNUSED_P(parser);
UNUSED_P(expectedExpandedSize);
#endif // XML_GE == 1
free((XML_Char *)entity->textPtr);
free(entity);
}
START_TEST(test_scan_entity_text) {
// Speed up the test, chunk size expected to not matter in this test
if (g_chunkSize != 0)
return;
const char *const doc = "<!DOCTYPE doc[\n"
" <!ENTITY ten '123456789a'>\n"
"]>\n"
"<doc/>";
XML_Parser parser = XML_ParserCreate(NULL);
assert_true(parser != NULL);
assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
/*isFinal=*/XML_TRUE)
== XML_STATUS_OK);
assert_entity_expanded_size_equals(parser, "", 0);
assert_entity_expanded_size_equals(parser, "123", 3);
assert_entity_expanded_size_equals(parser, "&amp;&apos;&gt;&lt;&quot;",
strlen("&amp;&apos;&gt;&lt;&quot;") + 5);
assert_entity_expanded_size_equals(parser, "&undefined;",
strlen("&undefined;"));
// Extract entity "ten" and set its `.expandedSize` so that we can scan text
// that refers to "&ten;" without blocking
#if XML_GE == 1
ENTITY *entityTen = NULL;
#endif
{
ENTITY *const entityReferencingTen
= createGeneralEntityWithText("referencing &ten;");
assert_true(entityReferencingTen != NULL);
#if XML_GE == 1
assert_true(scanEntityText(parser, entityReferencingTen,
&entityReferencingTen->scanPtr,
&entityReferencingTen->expandedSize, &entityTen)
== ENTITY_SCAN_BLOCKED);
assert_true(entityTen != NULL);
assert_true(entityReferencingTen->expandedSize == strlen("referencing "));
assert_true(entityReferencingTen->scanPtr
== entityReferencingTen->textPtr
+ entityReferencingTen->expandedSize);
XML_Char *const ten = xcsdup_str("ten");
assert_true(xcscmp(entityTen->name, ten) == 0);
free(ten);
entityTen->expandedSize = 10; // which makes it stop being a blocker
#endif // XML_GE == 1
free((XML_Char *)entityReferencingTen->textPtr);
free(entityReferencingTen);
}
assert_entity_expanded_size_equals(parser, "&ten;",
strlen("&ten;") + strlen("123456789a"));
assert_entity_expanded_size_equals(
parser, "[&ten;&ten;|&ten;]",
3 * (strlen("&ten;") + strlen("123456789a")) + strlen("[|]"));
// Cases where no entities should be recognized
assert_entity_expanded_size_equals(
parser, "<![CDATA[&ten;]]><!--&ten;--><?php &ten;?>",
strlen("<![CDATA[&ten;]]><!--&ten;--><?php &ten;?>"));
assert_entity_expanded_size_equals(parser, "<![CDATA[&ten;",
strlen("<![CDATA[&ten;"));
assert_entity_expanded_size_equals(parser, "<!--&ten;", strlen("<!--&ten;"));
assert_entity_expanded_size_equals(parser, "<?php &ten;",
strlen("<?php &ten;"));
XML_ParserFree(parser);
}
END_TEST
void
make_entities_test_case(Suite *s) {
TCase *const tc_entities = tcase_create("entities tests");
suite_add_tcase(s, tc_entities);
tcase_add_test(tc_entities, test_is_cdata_comment_or_pi_start);
tcase_add_test(tc_entities, test_is_predefined_entity_name);
tcase_add_test(tc_entities, test_scan_entity_text);
}
+41
View File
@@ -0,0 +1,41 @@
/* Unit tests related to internal handling of entities
__ __ _
___\ \/ /_ __ __ _| |_
/ _ \\ /| '_ \ / _` | __|
| __// \| |_) | (_| | |_
\___/_/\_\ .__/ \__,_|\__|
|_| XML parser
Copyright (c) 2026 Sebastian Pipping <[email protected]>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
SPDX-License-Identifier: MIT
*/
#ifndef XML_ENTITIES_TESTS_H
#define XML_ENTITIES_TESTS_H
#include "minicheck.h"
extern void make_entities_test_case(Suite *s);
#endif /* XML_ENTITIES_TESTS_H */
+1
View File
@@ -51,6 +51,7 @@
#include "expat_config.h"
#include "expat.h"
#include "chardata.h"
/* Variable holding the expected handler userData */
extern const void *g_handler_data;
+180 -1
View File
@@ -25,6 +25,7 @@
Copyright (c) 2026 Evgeny Kotkov <[email protected]>
Copyright (c) 2026 Darren Carreras <[email protected]>
Copyright (c) 2026 Junki Lee <[email protected]>
Copyright (c) 2026 Pu Cheng <[email protected]>
Licensed under the MIT license:
Permission is hereby granted, free of charge, to any person obtaining
@@ -60,6 +61,7 @@
#include <stdint.h>
#include <string.h>
#include "dummy.h" // for dummy_start_element
#include "expat.h"
#include "internal.h"
#include "minicheck.h"
@@ -222,7 +224,7 @@ START_TEST(test_misc_version) {
if (! versions_equal(&read_version, &parsed_version))
fail("Version mismatch");
if (xcscmp(version_text, XCS("expat_2.9.0"))
if (xcscmp(version_text, XCS("expat_2.10.0"))
!= 0) /* needs bump on releases */
fail("XML_*_VERSION in expat.h out of sync?\n");
}
@@ -591,6 +593,26 @@ START_TEST(test_misc_stopparser_rejects_unstarted_parser) {
}
END_TEST
static void *
nonzero_filling_malloc(size_t size) {
void *const ptr = malloc(size);
if (ptr != NULL)
memset(ptr, 0xA5, size);
return ptr;
}
START_TEST(test_misc_parsing_status_of_unstarted_parser) {
XML_Memory_Handling_Suite memsuite = {nonzero_filling_malloc, realloc, free};
XML_Parser parser = XML_ParserCreate_MM(NULL, &memsuite, NULL);
assert_true(parser != NULL);
XML_ParsingStatus status = {.parsing = XML_PARSING, .finalBuffer = XML_TRUE};
XML_GetParsingStatus(parser, &status);
assert_true(status.parsing == XML_INITIALIZED);
assert_true(status.finalBuffer == XML_FALSE);
XML_ParserFree(parser);
}
END_TEST
/* Adaptation of accumulate_characters that takes ExtHdlrData input to work with
* test_renter_loop_finite_content below */
void XMLCALL
@@ -1024,6 +1046,159 @@ START_TEST(test_misc_input_2gb) {
}
END_TEST
static void
dummy_start_doctype(void *userData, const XML_Char *name,
const XML_Char *systemId, const XML_Char *publicId,
int has_internal_subset) {
UNUSED_P(userData);
UNUSED_P(name);
UNUSED_P(systemId);
UNUSED_P(publicId);
UNUSED_P(has_internal_subset);
}
static int
reset_start_doctype_decl_handler_not_standalone(void *userData) {
XML_Parser parser = userData;
XML_SetStartDoctypeDeclHandler(parser, NULL);
return XML_STATUS_OK;
}
START_TEST(test_misc_start_doctype_decl_handler_reset) {
// Speed up the test, chunk size expected to not matter in this test
if (g_chunkSize != 0)
return;
const char *const doc = "<!DOCTYPE r SYSTEM \"s\"><r/>";
XML_Parser parser = XML_ParserCreate(NULL);
XML_SetUserData(parser, parser);
XML_SetStartDoctypeDeclHandler(parser, dummy_start_doctype);
XML_SetNotStandaloneHandler(parser,
reset_start_doctype_decl_handler_not_standalone);
assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
/*isFinal=*/XML_TRUE)
== XML_STATUS_OK);
XML_ParserFree(parser);
}
END_TEST
START_TEST(test_misc_storeatts_attssize_allocation) {
// Speed up the test, chunk size expected to not matter in this test
if (g_chunkSize != 0)
return;
// NOTE: This has 17 attributes defined because `INIT_ATTS_SIZE` is 16.
// Array size is calculated as
// parser->m_attsSize = n + nDefaultAtts + INIT_ATTS_SIZE
// when I expected it would have needed to be
// parser->m_attsSize = n + 2 * nDefaultAtts + INIT_ATTS_SIZE .
// Please see the in-code comments in `storeAtts` for the black magic
// that makes it work nonetheless. The same memory is used as an array
// of `ATTRIBUTE` elements first, and then as an array of `XML_Char *`
// elements after.
const char *const doc = "<!DOCTYPE doc [\n"
" <!ELEMENT doc EMPTY>\n"
" <!ATTLIST doc\n"
" a01 CDATA '01'\n"
" a02 CDATA '02'\n"
" a03 CDATA '03'\n"
" a04 CDATA '04'\n"
" a05 CDATA '05'\n"
" a06 CDATA '06'\n"
" a07 CDATA '07'\n"
" a08 CDATA '08'\n"
" a09 CDATA '09'\n"
" a10 CDATA '10'\n"
" a11 CDATA '11'\n"
" a12 CDATA '12'\n"
" a13 CDATA '13'\n"
" a14 CDATA '14'\n"
" a15 CDATA '15'\n"
" a16 CDATA '16'\n"
" a17 CDATA '17'\n"
" >\n"
"]>\n"
"<doc/>\n";
init_dummy_handlers();
XML_Parser parser = XML_ParserCreate(NULL);
XML_SetStartElementHandler(
parser, dummy_start_element); // i.e. ask for calling `storeAtts`
assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
/*isFinal=*/XML_TRUE));
XML_ParserFree(parser);
assert_true(get_dummy_handler_flags() == DUMMY_START_ELEMENT_HANDLER_FLAG);
}
END_TEST
static void
comparingPiHandler(void *userData, const XML_Char *target,
const XML_Char *data) {
UNUSED_P(userData);
XML_Char *const literalTarget = xcsdup_str("target");
XML_Char *const literalData = xcsdup_str("data");
assert_true(literalTarget != NULL);
assert_true(literalData != NULL);
assert_true(xcscmp(target, literalTarget) == 0);
assert_true(xcscmp(data, literalData) == 0);
free(literalTarget);
free(literalData);
}
static int XMLCALL
piParsingExternalEntityHandler(XML_Parser parent, const XML_Char *context,
const XML_Char *base, const XML_Char *systemId,
const XML_Char *publicId) {
UNUSED_P(base);
UNUSED_P(systemId);
UNUSED_P(publicId);
const char *const doc = "<?target data?>";
XML_Parser subParser = XML_ExternalEntityParserCreate(parent, context, NULL);
assert_true(subParser != NULL);
// NOTE: Related test "test_empty_ext_param_entity_in_value" could be of
// interest
assert_true(_XML_Parse_SINGLE_BYTES(subParser, "", 0, /*isFinal=*/XML_FALSE)
== XML_STATUS_OK);
assert_true(_XML_Parse_SINGLE_BYTES(subParser, doc, (int)strlen(doc),
/*isFinal=*/XML_TRUE)
== XML_STATUS_OK);
XML_ParserFree(subParser);
return XML_STATUS_OK;
}
START_TEST(test_misc_empty_nonfinal_subparser_parse) {
const char *const doc = "<!DOCTYPE root [\n"
" <!ENTITY ext SYSTEM 'external.dtd'>\n"
"]>\n"
"<root>&ext;</root>\n";
XML_Parser parser = XML_ParserCreate(NULL);
assert_true(parser != NULL);
XML_SetProcessingInstructionHandler(parser, comparingPiHandler);
XML_SetExternalEntityRefHandler(parser, piParsingExternalEntityHandler);
assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
/*isFinal=*/XML_TRUE));
XML_ParserFree(parser);
}
END_TEST
void
make_miscellaneous_test_case(Suite *s) {
TCase *tc_misc = tcase_create("miscellaneous tests");
@@ -1050,6 +1225,7 @@ make_miscellaneous_test_case(Suite *s) {
tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak);
tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
tcase_add_test(tc_misc, test_misc_parsing_status_of_unstarted_parser);
tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
tcase_add_test(tc_misc, test_misc_sync_entity_tolerated);
@@ -1064,4 +1240,7 @@ make_miscellaneous_test_case(Suite *s) {
test_misc_unknown_encoding_release_getter_parser_reset);
tcase_add_test(tc_misc, test_misc_input_2gb);
tcase_add_test(tc_misc, test_misc_low_surrogate_mozilla_bug_2053153);
tcase_add_test(tc_misc, test_misc_storeatts_attssize_allocation);
tcase_add_test(tc_misc, test_misc_start_doctype_decl_handler_reset);
tcase_add_test(tc_misc, test_misc_empty_nonfinal_subparser_parse);
}
+2
View File
@@ -57,6 +57,7 @@
#include "acc_tests.h"
#include "alloc_tests.h"
#include "basic_tests.h"
#include "entities_tests.h"
#include "hash_tests.h"
#include "misc_tests.h"
#include "ns_tests.h"
@@ -75,6 +76,7 @@ make_suite(void) {
#endif
make_alloc_test_case(s);
make_basic_test_case(s);
make_entities_test_case(s);
make_hash_test_case(s);
make_miscellaneous_test_case(s);
make_namespace_test_case(s);
+23
View File
@@ -76,6 +76,28 @@ START_TEST(test_xcs_ncmp) {
}
END_TEST
START_TEST(test_xcs_nstr) {
if (g_chunkSize != 0)
return;
const XML_Char haystack[] = {ASCII_h, ASCII_a, ASCII_y, ASCII_s, ASCII_t,
ASCII_a, ASCII_c, ASCII_k, '\0'};
const XML_Char hay[] = {ASCII_h, ASCII_a, ASCII_y, '\0'};
const XML_Char stack[] = {ASCII_s, ASCII_t, ASCII_a, ASCII_c, ASCII_k, '\0'};
const XML_Char other[] = {ASCII_o, ASCII_t, ASCII_h, ASCII_e, ASCII_r, '\0'};
assert_true(xcsnstr(haystack, haystack, xcslen(haystack)) == haystack);
assert_true(xcsnstr(haystack, hay, xcslen(haystack)) == haystack);
assert_true(xcsnstr(haystack, hay, xcslen(hay)) == haystack);
assert_true(xcsnstr(haystack, hay, xcslen(hay) - 1) == NULL);
assert_true(xcsnstr(haystack, stack, xcslen(haystack))
== haystack + xcslen(hay));
assert_true(xcsnstr(haystack, other, xcslen(haystack)) == NULL);
assert_true(xcsnstr(haystack, empty, xcslen(haystack)) == haystack);
assert_true(xcsnstr(hay, haystack, xcslen(hay)) == NULL);
}
END_TEST
void
make_xcs_test_case(Suite *s) {
TCase *const tc_xcs = tcase_create("xcs tests");
@@ -83,4 +105,5 @@ make_xcs_test_case(Suite *s) {
tcase_add_test(tc_xcs, test_xcs_len);
tcase_add_test(tc_xcs, test_xcs_cmp);
tcase_add_test(tc_xcs, test_xcs_ncmp);
tcase_add_test(tc_xcs, test_xcs_nstr);
}
+3 -2
View File
@@ -7,6 +7,7 @@
# |_| XML parser
#
# Copyright (c) 2017-2026 Sebastian Pipping <[email protected]>
# Copyright (c) 2026 Funda Wang <[email protected]>
# Licensed under the MIT license:
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -30,7 +31,7 @@
bin_PROGRAMS = xmlwf
xmlwf_LDADD = ../lib/libexpat.la
xmlwf_LDADD = ../lib/libexpat.la @LIBM@
xmlwf_SOURCES = \
../lib/xcs.c \
xmlwf.c \
@@ -39,7 +40,7 @@ xmlwf_SOURCES = \
@[email protected]
xmlwf_CPPFLAGS = @AM_CPPFLAGS@ -I$(srcdir)/../lib
xmlwf_LDFLAGS = @AM_LDFLAGS@ @LIBM@
xmlwf_LDFLAGS = @AM_LDFLAGS@
if MINGW
if UNICODE
+3 -2
View File
@@ -23,6 +23,7 @@
# |_| XML parser
#
# Copyright (c) 2017-2026 Sebastian Pipping <[email protected]>
# Copyright (c) 2026 Funda Wang <[email protected]>
# Licensed under the MIT license:
#
# Permission is hereby granted, free of charge, to any person obtaining
@@ -378,7 +379,7 @@ target_alias = @target_alias@
top_build_prefix = @top_build_prefix@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
xmlwf_LDADD = ../lib/libexpat.la
xmlwf_LDADD = ../lib/libexpat.la @LIBM@
xmlwf_SOURCES = \
../lib/xcs.c \
xmlwf.c \
@@ -387,7 +388,7 @@ xmlwf_SOURCES = \
@[email protected]
xmlwf_CPPFLAGS = @AM_CPPFLAGS@ -I$(srcdir)/../lib $(am__append_1)
xmlwf_LDFLAGS = @AM_LDFLAGS@ @LIBM@ $(am__append_2)
xmlwf_LDFLAGS = @AM_LDFLAGS@ $(am__append_2)
EXTRA_DIST = \
codepage.h \
ct.c \