diff options
60 files changed, 1993 insertions, 909 deletions
@@ -13,9 +13,159 @@ !! will be funded by the City of Munich as part of their !! !! Open Source Sabbatical (https://opensource.muenchen.de/sabbatical.html) !! !! — thank you! !! -!! Sebastian Pipping -- Berlin, 2026-08-03 !! +!! !! +!! If your business relies on Expat beyond January 2027, please consider !! +!! funding the maintenance of Expat to ensure its health and security for !! +!! you and others. Thank you! !! +!! !! +!! Sebastian Pipping -- Berlin, 2026-09-22 !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +Release 2.8.5 Tue September 22 2026 + Security fixes: + #1282 CVE-2026-93990 -- Reject high surrogates not followed by a + low surrogate during UTF-16 decoding; previously, malformed + UTF-16 could be smuggled into the application using Expat + and could cause arbitrary damage there, depending on how + malformed UTF-16 was handled inside the application; + validation was not their job but Expat's. This is similar + to past vulnerability CVE-2022-25235. + Upstream CVSS 3.1 vector: + AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (CVSS score: 9.8) + +/////////////////////////////////////////////////////////////////////////////// +// The next release will drop two (disabled-by-default) features: // +// // +// - ATTR_INFO (-DXML_ATTR_INFO, -DEXPAT_ATTR_INFO, --enable-xml-attr-info, // +// function XML_GetAttributeInfo, struct XML_AttrInfo) // +// - MIN_SIZE (-DXML_MIN_SIZE, -DEXPAT_MIN_SIZE) // +// // +// If you need them in 2026 and beyond, please share your scenario at // +// GitHub issues #1370 (for ATTR_INFO) and/or #1379 (for MIN_SIZE). Thanks! // +/////////////////////////////////////////////////////////////////////////////// + + Bug fixes: + #1346 lib: Fix OOM-related memory leak on a failed overflow check + #1371 lib: Fix memory alignment for architectures with 128bit + pointers like CHERI-RISC-V + #1367 xmlwf: Handle errors when closing output files + + Other changes: + #1354 lib: Reject an XML declaration version other than `1.[0-9]+` + (which is less strict than XML 1.0r4 (fourth edition) + and matches XML 1.0r5 (fifth edition)) + #1362 lib: Make Clang, GCC and MSVC warn about use of function + XML_SetHashSalt that is deprecated since Expat 2.8.0 + #1357 lib: Drop internal macros FASTCALL, PTRCALL, PTRFASTCALL + #1367 xmlwf: Document that with `-k` the last error determines the + xmlwf exit code in `--help` output + #1367 xmlwf: Make exit code 3 documentation match exit code 2 more + closely in `--help` output + #1352 #1353 CMake|Windows: Refrain from adding `/source-charset:utf-8` + for MSVC + #1366 #1374 Autotools: Be explicit about the minimum required version of + GNU Automake, currently version 1.13 of 2012-12-28 + #1351 Autotools|macOS: Sync CMake templates with CMake 4.4.3 + #1349 Replace some internal use of XML_Bool with standard bool + #1364 tests: Propagate xmltest.sh failures via exit status + #1360 tests|xmlwf: Add `#include "expat_config.h"` where missing + #1355 tests: Start covering hash table operation + #1350 #1369 tests: Drop __cplusplus leftovers + #1378 tests: Fix tail pointer when unlinking the last tracked + allocation + #1376 docs: Emphasize that XML_StopParser is not immediate + #1381 docs: Sync XML_FeatureEnum value list in doc/reference.html + #1356 #1361 Version info bumped from 13:4:12 (libexpat*.so.1.12.4) + to 13:5:12 (libexpat*.so.1.12.5); see https://verbump.de/ + for what these numbers do + + Infrastructure: + #1347 Add missing .gitignore entries + #1360 CI: Detect missing `#include "expat_config.h"` + #1368 CI: Bump MinGW Clang from 23.0.1 to 23.1.1 + #1377 CI: Bump Fil-C from 0.684 to 0.685 + #1380 CI: Bump Cppcheck from 2.21.0 to 2.22.0 + #1372 CI: Extract helper script `apply-htmltidy.sh` + #1366 #1374 Autotools: Start to also produce .tar.bz3 release tarballs + + Special thanks to: + Afonso Januário + Braian Plaku + Florian Schmaus + Huang Wenbin + Kamila Szewczyk + Kartik Kenchi + Leo Camus + Matthew Fernandez + Stan Ulbrych + and + City of Munich Open Source Sabbatical + +Release 2.8.4 Mon August 31 2026 + Security fixes: + #1321 #1331 CVE-2026-66046, CVE-2026-76641 -- Fix quadratic runtime from + "attribute isCdata lookups" that allowed denial of service + attacks through moderately sized crafted XML input + (CWE-407). + The vulnerability is closely related to past CVE-2026-45186 + that was fixed with Expat 2.8.1. + Please note that a layer of compression around XML can + significantly reduce the minimum attack payload size. + Upstream CVSS 3.1 vector: + AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (CVSS score: 7.5) + (Note the "AV:N" for network/remote.) + #1322 CVE-2026-76957 -- Protect custom encoding callbacks from + parser re-entry. The vulnerability is closely related to + past issues CVE-2026-50219, CVE-2026-56131 and + CVE-2026-56412 that were all fixed with Expat 2.8.2. + #1326 CVE-2026-76956 -- Fix inverted getentropy() return handling + Allows for hash flooding denial of services in + configurations where getentropy is configured or detected + as the only high quality entropy extractor. + Upstream CVSS 3.1 vector: + AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H (CVSS score: 5.9) + (Note the "AV:N" for network/remote.) + + Other changes: + #1332 #1333 CMake: Only add `/source-charset:utf-8` when `/utf-8` is not + present + #1315 lib: Resolve (currently unreachable) undefined behavior from + overshifting a signed int to the left + #1325 #1334 lib: Support read-only hash table lookup with keys that are + not zero-terminated + #1340 lib: Use a C99 bool for `ENTITY.open` + #1319 Fix typo in comment + #1320 Sync file headers + #1328 #1329 Version info bumped from 13:3:12 (libexpat*.so.1.12.3) + to 13:4:12 (libexpat*.so.1.12.4); see https://verbump.de/ + for what these numbers do + + Infrastructure: + #1317 #1335 CI: Cover compilation and execution with Fil-C + #1337 CI: Cover compilation and execution on riscv64 + #1338 CI: Cover compilation and execution with Clang-based MinGW + #1339 CI: Cover compilation and execution on (big-endian) s390x + #1316 CI: Run test suite with musl, also + #1336 CI: Bump WASI SDK from 33 to 34 + #1345 CI: Bump Clang from 22 to 23 + + Special thanks to: + Alberto Maschietto + Alexander Bluhm + Berkay Eren Ürün + Darren Carreras + Fabian Wahle (Hap Security) + Matteo Forzan + Matthew Fernandez + Sorrashut Kaewtaworn + Wade Sparks III + Zeyou Liu + and + City of Munich Open Source Sabbatical + Moonshot AI + VulnCheck + Z.ai + Release 2.8.3 Mon August 10 2026 Security fixes: #1296 CVE-2026-72522 -- Fix an out-of-bounds read and the resulting diff --git a/Makefile.am b/Makefile.am index 09b88f315165..fbc03d85f474 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,13 +33,6 @@ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE # USE OR OTHER DEALINGS IN THE SOFTWARE. -AUTOMAKE_OPTIONS = \ - dist-bzip2 \ - dist-lzip \ - dist-xz \ - foreign \ - subdir-objects - ACLOCAL_AMFLAGS = -I m4 LIBTOOLFLAGS = --verbose diff --git a/Makefile.in b/Makefile.in index 0f9a441aa882..c0a0eefc47ac 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. +# Makefile.in generated by automake 1.19 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2025 Free Software Foundation, Inc. +# Copyright (C) 1994-2026 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -261,7 +261,13 @@ am__remove_distdir = \ ; rm -rf "$(distdir)" \ || { sleep 5 && rm -rf "$(distdir)"; }; \ else :; fi -am__post_remove_distdir = $(am__remove_distdir) +am__post_remove_distdir = \ + $(am__is_gnu_make) || rm -f am__distdir.tar; \ + $(am__remove_distdir) +am__ensure_distdir_tar = \ + test -f am__distdir.tar || $(MAKE) $(AM_MAKEFLAGS) am__distdir.tar +am__dist_compress_failed = \ + am__rc=$$?; rm -f $$am__archive am__distdir.tar; exit $$am__rc am__relativize = \ dir0=`pwd`; \ sed_first='s,^\([^/]*\)/.*$$,\1,'; \ @@ -287,10 +293,13 @@ am__relativize = \ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ done; \ reldir="$$dir2" -DIST_ARCHIVES = $(distdir).tar.gz $(distdir).tar.bz2 $(distdir).tar.lz \ - $(distdir).tar.xz +# Exists only to be overridden by the user if desired. +AM_DIST_TAR_IGNORE_STDERR = $(am__tar_ignore_stderr) +DIST_ARCHIVES = $(distdir).tar.gz $(distdir).tar.bz2 \ + $(distdir).tar.bz3 $(distdir).tar.lz $(distdir).tar.xz GZIP_ENV = -9 -DIST_TARGETS = dist-lzip dist-xz dist-bzip2 dist-gzip +DIST_TARGETS = am--dist-lzip am--dist-xz am--dist-bzip2 am--dist-bzip3 \ + am--dist-gzip # Exists only to be overridden by the user if desired. AM_DISTCHECK_DVI_TARGET = dvi distuninstallcheck_listfiles = find . -type f -print @@ -411,6 +420,7 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ +am__tar_ignore_stderr = @am__tar_ignore_stderr@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ @@ -454,13 +464,6 @@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -AUTOMAKE_OPTIONS = \ - dist-bzip2 \ - dist-lzip \ - dist-xz \ - foreign \ - subdir-objects - ACLOCAL_AMFLAGS = -I m4 LIBTOOLFLAGS = --verbose SUBDIRS = lib $(am__append_1) $(am__append_2) $(am__append_3) @@ -748,13 +751,14 @@ distdir-am: $(DISTFILES) $(AM_V_at)$(MKDIR_P) "$(distdir)" @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + am__distdir="$(distdir)"; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sed "/\//!d;s|^|$$am__distdir/|;s,/[^/]*$$,," | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ @@ -807,55 +811,96 @@ distdir-am: $(DISTFILES) ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ || chmod -R a+r "$(distdir)" -dist-gzip: distdir - tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz + +am__distdir.tar: distdir + am__distdir="$(distdir)"; \ + am__tar_msg=`tardir=$$am__distdir && $(am__tar) 2>&1 >am__distdir.tar`; \ + am__tar_rc=$$?; \ + test -z "$$am__tar_msg" || { printf '%s\n' "$$am__tar_msg" >&2; \ + test x"$(AM_DIST_TAR_IGNORE_STDERR)" = xyes || am__tar_rc=1; }; \ + test $$am__tar_rc -eq 0 || { rm -f am__distdir.tar; \ + echo "$(distdir).tar: cannot create the distribution archive" >&2; exit 1; } + +.INTERMEDIATE: am__distdir.tar +dist-gzip: am--dist-gzip $(am__post_remove_distdir) -dist-bzip2: distdir - tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 +am--dist-gzip: am__distdir.tar + @$(am__ensure_distdir_tar) + am__archive=$(distdir).tar.gz; \ + eval GZIP= gzip $(GZIP_ENV) -c <am__distdir.tar >$$am__archive \ + || { $(am__dist_compress_failed); } +dist-bzip2: am--dist-bzip2 $(am__post_remove_distdir) - -dist-bzip3: distdir - tardir=$(distdir) && $(am__tar) | bzip3 -c >$(distdir).tar.bz3 +am--dist-bzip2: am__distdir.tar + @$(am__ensure_distdir_tar) + am__archive=$(distdir).tar.bz2; \ + BZIP2=$${BZIP2--9} bzip2 -c <am__distdir.tar >$$am__archive \ + || { $(am__dist_compress_failed); } +dist-bzip3: am--dist-bzip3 $(am__post_remove_distdir) -dist-lzip: distdir - tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz +am--dist-bzip3: am__distdir.tar + @$(am__ensure_distdir_tar) + am__archive=$(distdir).tar.bz3; \ + bzip3 -c <am__distdir.tar >$$am__archive \ + || { $(am__dist_compress_failed); } +dist-lzip: am--dist-lzip $(am__post_remove_distdir) -dist-xz: distdir - tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz +am--dist-lzip: am__distdir.tar + @$(am__ensure_distdir_tar) + am__archive=$(distdir).tar.lz; \ + lzip -c $${LZIP_OPT--9} <am__distdir.tar >$$am__archive \ + || { $(am__dist_compress_failed); } +dist-xz: am--dist-xz $(am__post_remove_distdir) +am--dist-xz: am__distdir.tar + @$(am__ensure_distdir_tar) + am__archive=$(distdir).tar.xz; \ + XZ_OPT=$${XZ_OPT--e} xz -c <am__distdir.tar >$$am__archive \ + || { $(am__dist_compress_failed); } -dist-zstd: distdir - tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst +dist-zstd: am--dist-zstd $(am__post_remove_distdir) +am--dist-zstd: am__distdir.tar + @$(am__ensure_distdir_tar) + am__archive=$(distdir).tar.zst; \ + zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} <am__distdir.tar >$$am__archive \ + || { $(am__dist_compress_failed); } -dist-tarZ: distdir +dist-tarZ: am--dist-tarZ + $(am__post_remove_distdir) +am--dist-tarZ: am__distdir.tar + @$(am__ensure_distdir_tar) @echo WARNING: "Support for distribution archives compressed with" \ "legacy program 'compress' is deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 - tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z - $(am__post_remove_distdir) + am__archive=$(distdir).tar.Z; \ + compress -c <am__distdir.tar >$$am__archive \ + || { $(am__dist_compress_failed); } -dist-shar: distdir +dist-shar: am--dist-shar + $(am__post_remove_distdir) +am--dist-shar: distdir @echo WARNING: "Support for shar distribution archives is" \ "deprecated." >&2 @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 - shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz - $(am__post_remove_distdir) + shar $(distdir) >$(distdir).shar || { rm -f $(distdir).shar; exit 1; } + eval GZIP= gzip $(GZIP_ENV) -f $(distdir).shar -dist-zip: distdir +dist-zip: am--dist-zip + $(am__post_remove_distdir) +am--dist-zip: distdir -rm -f $(distdir).zip zip -rq $(distdir).zip $(distdir) - $(am__post_remove_distdir) dist dist-all: - $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' + $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) $(am__post_remove_distdir) # This target untars the dist file and tries a VPATH configuration. Then # it guarantees that the distribution is self-contained by making another # tarfile. distcheck: dist - case '$(DIST_ARCHIVES)' in \ + case "$(DIST_ARCHIVES)" in \ *.tar.gz*) \ eval GZIP= gzip -dc $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ @@ -879,11 +924,13 @@ distcheck: dist chmod u+w $(distdir) mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst chmod a-w $(distdir) - test -d $(distdir)/_build || exit 0; \ - dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + am__distdir="$(distdir)"; \ + am__distarchives="$(DIST_ARCHIVES)"; \ + test -d "$$am__distdir/_build" || exit 0; \ + dc_install_base=`$(am__cd) "$$am__distdir/_inst" && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ && am__cwd=`pwd` \ - && $(am__cd) $(distdir)/_build/sub \ + && $(am__cd) "$$am__distdir/_build/sub" \ && ../../configure \ $(AM_DISTCHECK_CONFIGURE_FLAGS) \ $(DISTCHECK_CONFIGURE_FLAGS) \ @@ -906,13 +953,13 @@ distcheck: dist } || { rm -rf "$$dc_destdir"; exit 1; }) \ && rm -rf "$$dc_destdir" \ && $(MAKE) $(AM_MAKEFLAGS) dist \ - && rm -rf $(DIST_ARCHIVES) \ + && rm -rf $$am__distarchives \ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ && cd "$$am__cwd" \ || exit 1 $(am__post_remove_distdir) @(echo "$(distdir) archives ready for distribution: "; \ - list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + list="$(DIST_ARCHIVES)"; for i in $$list; do echo $$i; done) | \ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' distuninstallcheck: @test -n '$(distuninstallcheck_dir)' || { \ @@ -1051,16 +1098,18 @@ uninstall-am: uninstall-nodist_cmakeDATA uninstall-pkgconfigDATA .MAKE: $(am__recursive_targets) all install-am install-strip .PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ - am--refresh check check-am clean clean-cscope clean-generic \ - clean-libtool cscope cscopelist-am ctags ctags-am dist \ - dist-all dist-bzip2 dist-bzip3 dist-gzip dist-lzip dist-shar \ - dist-tarZ dist-xz dist-zip dist-zstd distcheck distclean \ - distclean-generic distclean-hdr distclean-libtool \ - distclean-tags distcleancheck distdir distuninstallcheck dvi \ - dvi-am html html-am info info-am install install-am \ - install-data install-data-am install-dvi install-dvi-am \ - install-exec install-exec-am install-html install-html-am \ - install-info install-info-am install-man \ + am--dist-bzip2 am--dist-bzip3 am--dist-gzip am--dist-lzip \ + am--dist-shar am--dist-tarZ am--dist-xz am--dist-zip \ + am--dist-zstd am--refresh check check-am clean clean-cscope \ + clean-generic clean-libtool cscope cscopelist-am ctags \ + ctags-am dist dist-all dist-bzip2 dist-bzip3 dist-gzip \ + dist-lzip dist-shar dist-tarZ dist-xz dist-zip dist-zstd \ + distcheck distclean distclean-generic distclean-hdr \ + distclean-libtool distclean-tags distcleancheck distdir \ + distuninstallcheck dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ install-nodist_cmakeDATA install-pdf install-pdf-am \ install-pkgconfigDATA install-ps install-ps-am install-strip \ installcheck installcheck-am installdirs installdirs-am \ diff --git a/README.md b/README.md index 3e75af77e500..36e46a90fd23 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ > Thank you! :heart: :pray: -# Expat, Release 2.8.3 +# Expat, Release 2.8.5 This is Expat, a C99 library for parsing [XML 1.0 Fourth Edition](https://www.w3.org/TR/2006/REC-xml-20060816/), started by diff --git a/configure.ac b/configure.ac index ce33dd5698c8..e8562dbc5fc1 100644 --- a/configure.ac +++ b/configure.ac @@ -74,7 +74,21 @@ AC_CONFIG_SRCDIR([Makefile.in]) AC_CONFIG_AUX_DIR([conftools]) AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_HOST -AM_INIT_AUTOMAKE + +dnl NOTE: Macro `AM_OPTIONAL_AUTOMAKE` is available with Automake >=1.19 +dnl and serves as a proxy for robust Automake >=1.19 detection here. +dnl `dist-bzip3` was introduced with Automake 1.18 already but there +dnl is no robust way of checking precisely for Automake >=1.18. +dnl Automake >=1.13 is needed for (1) our use of LOG_DRIVER and +dnl (2) making it work without asking for parallel tests, explicitly. +m4_ifdef([AM_OPTIONAL_AUTOMAKE], [ + AM_INIT_AUTOMAKE([1.19 dist-bzip2 dist-bzip3 dist-lzip dist-xz foreign subdir-objects]) + + dnl NOTE: Without this dummy call in the file the `m4_ifdef` above never hits true. + AM_OPTIONAL_AUTOMAKE([]) +], [ + AM_INIT_AUTOMAKE([1.13 dist-bzip2 dist-lzip dist-xz foreign subdir-objects]) +]) AM_MAINTAINER_MODE([enable]) # to allow argument --disable-maintainer-mode @@ -90,7 +104,7 @@ dnl If the API changes incompatibly set LIBAGE back to 0 dnl LIBCURRENT=13 # sync -LIBREVISION=3 # with +LIBREVISION=5 # with LIBAGE=12 # CMakeLists.txt! AC_CONFIG_HEADERS([expat_config.h]) diff --git a/doc/Makefile.in b/doc/Makefile.in index 0bda758420f0..52a18ffb673f 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. +# Makefile.in generated by automake 1.19 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2025 Free Software Foundation, Inc. +# Copyright (C) 1994-2026 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -309,6 +309,7 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ +am__tar_ignore_stderr = @am__tar_ignore_stderr@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ @@ -372,9 +373,9 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi exit 1;; \ esac; \ done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu doc/Makefile'; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign doc/Makefile'; \ $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu doc/Makefile + $(AUTOMAKE) --foreign doc/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -455,13 +456,14 @@ distdir: $(BUILT_SOURCES) distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + am__distdir="$(distdir)"; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sed "/\//!d;s|^|$$am__distdir/|;s,/[^/]*$$,," | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ diff --git a/doc/reference.html b/doc/reference.html index 022a2a0c043c..5bd9150ea78a 100644 --- a/doc/reference.html +++ b/doc/reference.html @@ -53,7 +53,7 @@ <body> <div> <h1> - The Expat XML Parser <small>Release 2.8.3</small> + The Expat XML Parser <small>Release 2.8.5</small> </h1> </div> @@ -1763,12 +1763,16 @@ XML_StopParser(XML_Parser p, </pre> <div class="fcndef"> <p> - Stops parsing, causing <code><a href="#XML_Parse">XML_Parse</a></code> or - <code><a href="#XML_ParseBuffer">XML_ParseBuffer</a></code> to return. Must be - called from within a call-back handler, except when aborting (when - <code>resumable</code> is <code>XML_FALSE</code>) an already suspended parser. - Some call-backs may still follow because they would otherwise get lost, - including + Stops parsing as soon as possible, causing <code><a href= + "#XML_Parse">XML_Parse</a></code> or <code><a href= + "#XML_ParseBuffer">XML_ParseBuffer</a></code> to return. Must be called from + within a call-back handler, except when aborting (when <code>resumable</code> + is <code>XML_FALSE</code>) an already suspended parser. + </p> + + <p> + <strong>Note:</strong> Some call-backs may still follow because they would + otherwise get lost, including </p> <ul> @@ -3709,8 +3713,20 @@ enum XML_FeatureEnum { XML_FEATURE_MIN_SIZE, XML_FEATURE_SIZEOF_XML_CHAR, XML_FEATURE_SIZEOF_XML_LCHAR, + /* Added in Expat 2.0.0. */ XML_FEATURE_NS, - XML_FEATURE_LARGE_SIZE + /* Added in Expat 2.0.1. */ + XML_FEATURE_LARGE_SIZE, + /* Added in Expat 2.1.0. */ + XML_FEATURE_ATTR_INFO, + /* Added in Expat 2.4.0. */ + XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT, + XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT, + /* Added in Expat 2.6.0. */ + XML_FEATURE_GE, + /* Added in Expat 2.7.2. */ + XML_FEATURE_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT, + XML_FEATURE_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT, }; typedef struct { diff --git a/doc/xmlwf.1 b/doc/xmlwf.1 index 639760bc6be4..e9b6a2fe4610 100644 --- a/doc/xmlwf.1 +++ b/doc/xmlwf.1 @@ -5,7 +5,7 @@ \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac -.TH XMLWF 1 "August 10, 2026" "" "" +.TH XMLWF 1 "September 22, 2026" "" "" .SH NAME xmlwf \- Determines if an XML document is well-formed .SH SYNOPSIS diff --git a/doc/xmlwf.xml b/doc/xmlwf.xml index b1b23bee1f6f..e7a2664b7e42 100644 --- a/doc/xmlwf.xml +++ b/doc/xmlwf.xml @@ -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>August 10, 2026</date>"> + <!ENTITY dhdate "<date>September 22, 2026</date>"> <!-- Please adjust this^^ date whenever cutting a new release. --> <!ENTITY dhsection "<manvolnum>1</manvolnum>"> <!ENTITY dhemail "<email>bronson@rinspin.com</email>"> diff --git a/examples/Makefile.in b/examples/Makefile.in index 56a6f69c07b7..4ef878f7867c 100644 --- a/examples/Makefile.in +++ b/examples/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. +# Makefile.in generated by automake 1.19 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2025 Free Software Foundation, Inc. +# Copyright (C) 1994-2026 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -337,6 +337,7 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ +am__tar_ignore_stderr = @am__tar_ignore_stderr@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ @@ -399,9 +400,9 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi exit 1;; \ esac; \ done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu examples/Makefile'; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign examples/Makefile'; \ $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu examples/Makefile + $(AUTOMAKE) --foreign examples/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -453,22 +454,25 @@ $(am__depfiles_remade): am--depfiles: $(am__depfiles_remade) .c.o: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< @@ -537,13 +541,14 @@ distdir: $(BUILT_SOURCES) distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + am__distdir="$(distdir)"; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sed "/\//!d;s|^|$$am__distdir/|;s,/[^/]*$$,," | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ diff --git a/fix-xmltest-log.sh b/fix-xmltest-log.sh index 4deafe53a7a8..e4dbd54275c7 100755 --- a/fix-xmltest-log.sh +++ b/fix-xmltest-log.sh @@ -38,6 +38,9 @@ exec "${sed}" -i.bak \ -e '# convert DOS line endings to Unix without resorting to dos2unix' \ -e $'s/\r//' \ \ + -e '# Filter out "unhandled instruction" lines from AddressSanitizer' \ + -e '/^==[0-9]\+==interception_win: unhandled instruction at /d' \ + \ -e 's/^wine: Call .* msvcrt\.dll\._wperror, aborting$/ibm49i02.dtd: No such file or directory/' \ \ -e '/^wine: /d' \ diff --git a/fuzz/xml_lpm_fuzzer.cpp b/fuzz/xml_lpm_fuzzer.cpp index 719629a6b547..190b34f74d98 100644 --- a/fuzz/xml_lpm_fuzzer.cpp +++ b/fuzz/xml_lpm_fuzzer.cpp @@ -8,6 +8,7 @@ Copyright (c) 2022 Mark Brand <markbrand@google.com> Copyright (c) 2025 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -383,7 +384,9 @@ UnknownEncodingHandler(void *encodingHandlerData, const XML_Char *name, void InitializeParser(XML_Parser parser) { XML_SetUserData(parser, (void *)parser); - XML_SetHashSalt(parser, 0x41414141); + const uint8_t entropy[16] = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41}; + XML_SetHashSalt16Bytes(parser, entropy); XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS); XML_SetElementDeclHandler(parser, ElementDeclHandler); diff --git a/fuzz/xml_parse_fuzzer.c b/fuzz/xml_parse_fuzzer.c index 29ab33ff79d9..1eb0ad299628 100644 --- a/fuzz/xml_parse_fuzzer.c +++ b/fuzz/xml_parse_fuzzer.c @@ -17,6 +17,7 @@ #include <assert.h> #include <limits.h> // for INT_MAX #include <stdint.h> +#include <string.h> #include "expat.h" #include "siphash.h" @@ -34,7 +35,8 @@ #endif // 16-byte deterministic hash key. -static unsigned char hash_key[16] = "FUZZING IS FUN!"; +static unsigned char hash_key_1[16] = "FUZZING IS FUN?"; +static unsigned char hash_key_2[16] = "FUZZING IS FUN!"; static void XMLCALL start(void *userData, const XML_Char *name, const XML_Char **atts) { @@ -59,8 +61,14 @@ may_stop_character_handler(void *userData, const XML_Char *s, int len) { static void ParseOneInput(XML_Parser p, const uint8_t *data, size_t size) { // Set the hash salt using siphash to generate a deterministic hash. - struct sipkey *key = sip_keyof(hash_key); - XML_SetHashSalt(p, (unsigned long)siphash24(data, size, key)); + // The salt is 16 bytes and siphash24 produces 8, so the input is hashed + // under two keys. + const uint64_t first = siphash24(data, size, sip_keyof(hash_key_1)); + const uint64_t second = siphash24(data, size, sip_keyof(hash_key_2)); + uint8_t entropy[16]; + memcpy(entropy, &first, sizeof(first)); + memcpy(entropy + sizeof(first), &second, sizeof(second)); + XML_SetHashSalt16Bytes(p, entropy); (void)sip24_valid; XML_SetUserData(p, p); diff --git a/fuzz/xml_parsebuffer_fuzzer.c b/fuzz/xml_parsebuffer_fuzzer.c index 38b9981b0b50..a854f6706396 100644 --- a/fuzz/xml_parsebuffer_fuzzer.c +++ b/fuzz/xml_parsebuffer_fuzzer.c @@ -35,7 +35,8 @@ #endif // 16-byte deterministic hash key. -static unsigned char hash_key[16] = "FUZZING IS FUN!"; +static unsigned char hash_key_1[16] = "FUZZING IS FUN?"; +static unsigned char hash_key_2[16] = "FUZZING IS FUN!"; static void XMLCALL start(void *userData, const XML_Char *name, const XML_Char **atts) { @@ -60,8 +61,14 @@ may_stop_character_handler(void *userData, const XML_Char *s, int len) { static void ParseOneInput(XML_Parser p, const uint8_t *data, size_t size) { // Set the hash salt using siphash to generate a deterministic hash. - struct sipkey *key = sip_keyof(hash_key); - XML_SetHashSalt(p, (unsigned long)siphash24(data, size, key)); + // The salt is 16 bytes and siphash24 produces 8, so the input is hashed + // under two keys. + const uint64_t first = siphash24(data, size, sip_keyof(hash_key_1)); + const uint64_t second = siphash24(data, size, sip_keyof(hash_key_2)); + uint8_t entropy[16]; + memcpy(entropy, &first, sizeof(first)); + memcpy(entropy + sizeof(first), &second, sizeof(second)); + XML_SetHashSalt16Bytes(p, entropy); (void)sip24_valid; XML_SetUserData(p, p); diff --git a/lib/Makefile.am b/lib/Makefile.am index 2b6aec2e6bdc..6b0d0af4e3c5 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -115,6 +115,7 @@ EXTRA_DIST = \ expat_external.h \ expat.h \ fallthrough.h \ + hash_table.h \ iasciitab.h \ internal.h \ latin1tab.h \ diff --git a/lib/Makefile.in b/lib/Makefile.in index 73db0e584705..58b19912b596 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. +# Makefile.in generated by automake 1.19 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2025 Free Software Foundation, Inc. +# Copyright (C) 1994-2026 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -444,6 +444,7 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ +am__tar_ignore_stderr = @am__tar_ignore_stderr@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ @@ -511,6 +512,7 @@ EXTRA_DIST = \ expat_external.h \ expat.h \ fallthrough.h \ + hash_table.h \ iasciitab.h \ internal.h \ latin1tab.h \ @@ -540,9 +542,9 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi exit 1;; \ esac; \ done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu lib/Makefile'; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/Makefile'; \ $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu lib/Makefile + $(AUTOMAKE) --foreign lib/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -643,22 +645,25 @@ $(am__depfiles_remade): am--depfiles: $(am__depfiles_remade) .c.o: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< @@ -832,13 +837,14 @@ distdir: $(BUILT_SOURCES) distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + am__distdir="$(distdir)"; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sed "/\//!d;s|^|$$am__distdir/|;s,/[^/]*$$,," | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ diff --git a/lib/expat.h b/lib/expat.h index dbebd985a652..4c3851d50a5f 100644 --- a/lib/expat.h +++ b/lib/expat.h @@ -20,6 +20,7 @@ Copyright (c) 2023 Sony Corporation / Snild Dolkow <snild@sony.com> Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp> Copyright (c) 2025 Matthew Fernandez <matthew.fernandez@gmail.com> + Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -921,7 +922,9 @@ XML_SetParamEntityParsing(XML_Parser parser, Returns 1 if successful, 0 when called after parsing has started. Note: If parser == NULL, the function will do nothing and return 0. DEPRECATED since Expat 2.8.0. + Please use XML_SetHashSalt16Bytes instead. */ +XML_ATTR_DEPRECATED("please use XML_SetHashSalt16Bytes instead") XMLPARSEAPI(int) XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt); @@ -1040,8 +1043,11 @@ enum XML_FeatureEnum { XML_FEATURE_MIN_SIZE, XML_FEATURE_SIZEOF_XML_CHAR, XML_FEATURE_SIZEOF_XML_LCHAR, + /* Added in Expat 2.0.0. */ XML_FEATURE_NS, + /* Added in Expat 2.0.1. */ XML_FEATURE_LARGE_SIZE, + /* Added in Expat 2.1.0. */ XML_FEATURE_ATTR_INFO, /* Added in Expat 2.4.0. */ XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT, @@ -1096,7 +1102,7 @@ XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled); */ # define XML_MAJOR_VERSION 2 # define XML_MINOR_VERSION 8 -# define XML_MICRO_VERSION 3 +# define XML_MICRO_VERSION 5 # ifdef __cplusplus } diff --git a/lib/expat_external.h b/lib/expat_external.h index 4cd1f3a49c35..cf80f960d6e1 100644 --- a/lib/expat_external.h +++ b/lib/expat_external.h @@ -16,6 +16,7 @@ Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2018 Yury Gribov <tetra2005@gmail.com> Copyright (c) 2026 Matthew Fernandez <matthew.fernandez@gmail.com> + Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -125,6 +126,17 @@ # define XML_ATTR_ALLOC_SIZE(x) # endif +/* Marks a function that Expat still provides but that callers should move off + of. */ +# if defined(__clang__) || defined(__GNUC__) +# define XML_ATTR_DEPRECATED(message) \ + __attribute__((__deprecated__(message))) +# elif defined(_MSC_VER) +# define XML_ATTR_DEPRECATED(message) __declspec(deprecated(message)) +# else +# define XML_ATTR_DEPRECATED(message) // empty i.e. no deprecation +# endif + # define XMLPARSEAPI(type) XMLIMPORT type XMLCALL # ifdef __cplusplus diff --git a/lib/hash_table.h b/lib/hash_table.h new file mode 100644 index 000000000000..f290aad18693 --- /dev/null +++ b/lib/hash_table.h @@ -0,0 +1,78 @@ +/* Hash table related internal API + __ __ _ + ___\ \/ /_ __ __ _| |_ + / _ \\ /| '_ \ / _` | __| + | __// \| |_) | (_| | |_ + \___/_/\_\ .__/ \__,_|\__| + |_| XML parser + + Copyright (c) 2026 Sebastian Pipping <sebastian@pipping.org> + 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(HASH_TABLE_H) +# define HASH_TABLE_H 1 + +# include "expat.h" // for XML_Bool, XML_Parser +# include "internal.h" // for XML_NONTESTING_STATIC + +# include <stddef.h> // for size_t + +typedef const XML_Char *KEY; + +typedef struct { + KEY name; +} NAMED; + +typedef struct { + NAMED **v; + unsigned char power; + size_t size; + size_t used; + XML_Parser parser; +} HASH_TABLE; + +typedef struct { + NAMED **p; + NAMED **end; +} HASH_TABLE_ITER; + +XML_NONTESTING_STATIC NAMED *lookupWithLength(XML_Parser parser, + HASH_TABLE *table, KEY name, + size_t nameLen, + size_t createSize); +XML_NONTESTING_STATIC NAMED *lookup(XML_Parser parser, HASH_TABLE *table, + KEY name, size_t createSize); + +XML_NONTESTING_STATIC void hashTableInit(HASH_TABLE *table, XML_Parser parser); +XML_NONTESTING_STATIC void hashTableClear(HASH_TABLE *table); +XML_NONTESTING_STATIC void hashTableDestroy(HASH_TABLE *table); +XML_NONTESTING_STATIC void hashTableIterInit(HASH_TABLE_ITER *iter, + const HASH_TABLE *table); +XML_NONTESTING_STATIC NAMED *hashTableIterNext(HASH_TABLE_ITER *iter); + +XML_NONTESTING_STATIC XML_Bool keyeq(KEY s1, size_t s1len, KEY s2); +XML_NONTESTING_STATIC size_t keylen(KEY s); + +#endif // ! defined(HASH_TABLE_H) diff --git a/lib/internal.h b/lib/internal.h index 7e67d2e378c5..86655371d290 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -6,13 +6,6 @@ The following calling convention macros are defined for frequently called functions: - FASTCALL - Used for those internal functions that have a simple - body and a low number of arguments and local variables. - - PTRCALL - Used for functions called though function pointers. - - PTRFASTCALL - Like PTRCALL, but for low number of arguments. - inline - Used for selected internal functions for which inlining may improve performance on some platforms. @@ -33,6 +26,9 @@ Copyright (c) 2019 David Loffredo <loffredo@steptools.com> Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com> Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp> + Copyright (c) 2026 Matthew Wozniczka <mattheww@simba.com> + Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com> + Copyright (c) 2026 Florian Schmaus <florian.schmaus@codasip.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -57,132 +53,104 @@ SPDX-License-Identifier: MIT */ -#if defined(__GNUC__) && defined(__i386__) && ! defined(__MINGW32__) -/* We'll use this version by default only where we know it helps. - - regparm() generates warnings on Solaris boxes. See SF bug #692878. - - Instability reported with egcs on a RedHat Linux 7.3. - Let's comment out: - #define FASTCALL __attribute__((stdcall, regparm(3))) - and let's try this: -*/ -# define FASTCALL __attribute__((regparm(3))) -# define PTRFASTCALL __attribute__((regparm(3))) -#endif - -/* Using __fastcall seems to have an unexpected negative effect under - MS VC++, especially for function pointers, so we won't use it for - now on that platform. It may be reconsidered for a future release - if it can be made more effective. - Likely reason: __fastcall on Windows is like stdcall, therefore - the compiler cannot perform stack optimizations for call clusters. -*/ - -/* Make sure all of these are defined if they aren't already. */ - -#ifndef FASTCALL -# define FASTCALL -#endif - -#ifndef PTRCALL -# define PTRCALL -#endif +#if ! defined(INTERNAL_H) +# define INTERNAL_H 1 -#ifndef PTRFASTCALL -# define PTRFASTCALL -#endif +# ifndef XML_MIN_SIZE +# if ! defined(inline) +# ifdef __GNUC__ +# define inline __inline +# endif /* __GNUC__ */ +# endif +# endif /* XML_MIN_SIZE */ -#ifndef XML_MIN_SIZE -# if ! defined(__cplusplus) && ! defined(inline) -# ifdef __GNUC__ -# define inline __inline -# endif /* __GNUC__ */ -# endif -#endif /* XML_MIN_SIZE */ - -#ifdef __cplusplus -# define inline inline -#else # ifndef inline # define inline # endif -#endif -#include <limits.h> // ULONG_MAX -#include <stddef.h> // size_t +# if ! defined(XML_NONTESTING_STATIC) +# if defined(XML_TESTING) +# define XML_NONTESTING_STATIC // empty i.e. not static +# else +# define XML_NONTESTING_STATIC static +# endif +# endif + +# include <limits.h> // ULONG_MAX +# include <stddef.h> // size_t -#if defined(_WIN32) \ - && (! defined(__USE_MINGW_ANSI_STDIO) \ - || (1 - __USE_MINGW_ANSI_STDIO - 1 == 0)) -# define EXPAT_FMT_LLX(midpart) "%" midpart "I64x" -# define EXPAT_FMT_ULL(midpart) "%" midpart "I64u" -# if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW -# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d" -# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "I64u" +# if defined(_WIN32) \ + && (! defined(__USE_MINGW_ANSI_STDIO) \ + || (1 - __USE_MINGW_ANSI_STDIO - 1 == 0)) +# define EXPAT_FMT_LLX(midpart) "%" midpart "I64x" +# define EXPAT_FMT_ULL(midpart) "%" midpart "I64u" +# if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW +# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d" +# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "I64u" +# else +# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d" +# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u" +# endif # else -# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d" -# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u" +# 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 # 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 -#endif -#ifndef UNUSED_P -# define UNUSED_P(p) (void)p -#endif +# ifndef UNUSED_P +# define UNUSED_P(p) (void)p +# endif /* NOTE BEGIN If you ever patch these defaults to greater values for non-attack XML payload in your environment, please file a bug report with libexpat. Thank you! */ -#define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT \ - 100.0f -#define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT \ - 8388608 // 8 MiB, 2^23 +# define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT \ + 100.0f +# define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT \ + 8388608 // 8 MiB, 2^23 -#define EXPAT_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT 100.0f -#define EXPAT_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT \ - 67108864 // 64 MiB, 2^26 +# define EXPAT_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT 100.0f +# define EXPAT_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT \ + 67108864 // 64 MiB, 2^26 // NOTE: If function expat_alloc was user facing, EXPAT_MALLOC_ALIGNMENT would // have to take sizeof(long double) into account -#define EXPAT_MALLOC_ALIGNMENT sizeof(long long) // largest parser (sub)member -#define EXPAT_MALLOC_PADDING ((EXPAT_MALLOC_ALIGNMENT) - sizeof(size_t)) +union expat_align { + long long l; + void *p; +}; +# define EXPAT_MALLOC_ALIGNMENT sizeof(union expat_align) +# define EXPAT_MALLOC_PADDING ((EXPAT_MALLOC_ALIGNMENT) - sizeof(size_t)) /* NOTE END */ -#include "expat.h" // so we can use type XML_Parser below - -#ifdef __cplusplus -extern "C" { -#endif +# include "expat.h" // so we can use type XML_Parser below void _INTERNAL_trim_to_complete_utf8_characters(const char *from, const char **fromLimRef); -#if defined(XML_GE) && XML_GE == 1 +# if defined(XML_GE) && XML_GE == 1 unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser); unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser); const char *unsignedCharToPrintable(unsigned char c); -#endif +# endif extern -#if ! defined(XML_TESTING) +# if ! defined(XML_TESTING) const -#endif +# endif XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c -#if defined(XML_TESTING) +# if defined(XML_TESTING) + +int xmlSetHashSalt(XML_Parser parser, unsigned long hash_salt); + void *expat_malloc(XML_Parser parser, size_t size, int sourceLine); void expat_free(XML_Parser parser, void *ptr, int sourceLine); void *expat_realloc(XML_Parser parser, void *ptr, size_t size, int sourceLine); extern unsigned int g_bytesScanned; // used for testing only -#endif +# endif -#ifdef __cplusplus -} -#endif +#endif // not defined INTERNAL_H diff --git a/lib/random_getentropy.c b/lib/random_getentropy.c index d258df6a7678..ad8b1984fea0 100644 --- a/lib/random_getentropy.c +++ b/lib/random_getentropy.c @@ -54,7 +54,7 @@ bool writeRandomBytes_getentropy(void *target, size_t count) { errno = 0; - const bool success = getentropy(target, count); + const bool success = (getentropy(target, count) == 0); // MSan does not understand `getentropy`, so explain its effects if (success) MSAN_UNPOISON(target, count); diff --git a/lib/xmlparse.c b/lib/xmlparse.c index ac79b9ce7a3b..c3642bd52ff9 100644 --- a/lib/xmlparse.c +++ b/lib/xmlparse.c @@ -1,4 +1,4 @@ -/* ee5f82c3ffd57c5224394ba46f348dbce466d34d6c925a527ae46b1cfe6adf1d (2.8.3+) +/* 0864fe2d216f47b742263b698bc051c865b342e8b820e42c234717098ea507e3 (2.8.5+) __ __ _ ___\ \/ /_ __ __ _| |_ / _ \\ /| '_ \ / _` | __| @@ -51,6 +51,11 @@ Copyright (c) 2026 Kartik Kenchi <netliomax25@gmail.com> Copyright (c) 2026 Haris Hussain <hextheshadow0x@gmail.com> Copyright (c) 2026 Evgeny Kotkov <kotkov@apache.org> + Copyright (c) 2026 Darren Carreras <carrerasdarren@gmail.com> + Copyright (c) 2026 Alberto Maschietto <albertomaschietto9@gmail.com> + Copyright (c) 2026 Zeyou Liu <zeyouliu@tencent.com> + Copyright (c) 2026 Stan Ulbrych <stan@python.org> + Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -223,25 +228,10 @@ typedef char ICHAR; #define EXPAT_MIN(a, b) (((a) < (b)) ? (a) : (b)) #include "internal.h" +#include "hash_table.h" #include "xmltok.h" #include "xmlrole.h" -typedef const XML_Char *KEY; - -typedef struct { - KEY name; -} NAMED; - -typedef struct { - NAMED **v; - unsigned char power; - size_t size; - size_t used; - XML_Parser parser; -} HASH_TABLE; - -static size_t keylen(KEY s); - static void copy_salt_to_sipkey(XML_Parser parser, struct sipkey *key); /* For probing (after a collision) we need a step size relative prime @@ -257,11 +247,6 @@ static void copy_salt_to_sipkey(XML_Parser parser, struct sipkey *key); #define PROBE_STEP(hash, mask, power) \ ((unsigned char)((SECOND_HASH(hash, mask, power)) | 1)) -typedef struct { - NAMED **p; - NAMED **end; -} HASH_TABLE_ITER; - #define INIT_TAG_BUF_SIZE 32 /* must be a multiple of sizeof(XML_Char) */ #define INIT_DATA_BUF_SIZE 1024 #define INIT_ATTS_SIZE 16 @@ -330,13 +315,13 @@ typedef struct { const XML_Char *base; const XML_Char *publicId; const XML_Char *notation; - XML_Bool open; - XML_Bool hasMore; /* true if entity has not been completely processed */ - /* An entity can be open while being already completely processed (hasMore == - XML_FALSE). The reason is the delayed closing of entities until their inner + 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 */ - XML_Bool is_param; - XML_Bool is_internal; /* true if declared in internal subset outside PE */ + bool is_param; + bool is_internal; /* true if declared in internal subset outside PE */ } ENTITY; typedef struct { @@ -371,8 +356,8 @@ typedef struct { typedef struct attribute_id { XML_Char *name; PREFIX *prefix; - XML_Bool maybeTokenized; - XML_Bool xmlns; + bool maybeTokenized; + bool xmlns; } ATTRIBUTE_ID; typedef struct { @@ -381,6 +366,22 @@ typedef struct { const XML_Char *value; } DEFAULT_ATTRIBUTE; +// This structure allows mapping attribute names to instances of +// `DEFAULT_ATTRIBUTE`. +typedef struct { + // Member `name` goes first to make this structure compatible with structure + // `NAMED` (further up), which is needed to support use of structure + // `NAME_AND_DEFAULT_ATTRIBUTE` in a hash table as implemented by function + // `lookup` (further down). + const XML_Char *name; + // We would store a `DEFAULT_ATTRIBUTE *` here but the backing array + // can be reallocated which would invalidate the pointer. Using an index + // into the array instead, avoids that problem. + size_t attIndex; + // This is set to `false` by function `lookup`. + bool initialized; +} NAME_AND_DEFAULT_ATTRIBUTE; + typedef struct { unsigned long version; unsigned long hash; @@ -394,7 +395,7 @@ typedef struct { size_t nDefaultAtts; size_t allocDefaultAtts; DEFAULT_ATTRIBUTE *defaultAtts; - HASH_TABLE defaultAttsNames; + HASH_TABLE defaultAttForName; } ELEMENT_TYPE; typedef struct { @@ -412,12 +413,12 @@ typedef struct { XML_Bool standalone; #ifdef XML_DTD /* indicates if external PE has been read */ - XML_Bool paramEntityRead; + bool paramEntityRead; HASH_TABLE paramEntities; #endif /* XML_DTD */ PREFIX defaultPrefix; /* === scaffolding for building content model === */ - XML_Bool in_eldecl; + bool in_eldecl; CONTENT_SCAFFOLD *scaffold; unsigned contentStringLen; unsigned scaffSize; @@ -439,7 +440,7 @@ typedef struct open_internal_entity { struct open_internal_entity *next; ENTITY *entity; int startTagLevel; - XML_Bool betweenDecl; /* WFC: PE Between Declarations */ + bool betweenDecl; /* WFC: PE Between Declarations */ enum EntityType type; } OPEN_INTERNAL_ENTITY; @@ -476,8 +477,8 @@ typedef struct entity_stats { } ENTITY_STATS; #endif /* XML_GE == 1 */ -typedef enum XML_Error PTRCALL Processor(XML_Parser parser, const char *start, - const char *end, const char **endPtr); +typedef enum XML_Error Processor(XML_Parser parser, const char *start, + const char *end, const char **endPtr); static Processor prologProcessor; static Processor prologInitProcessor; @@ -509,7 +510,7 @@ static enum XML_Error doProlog(XML_Parser parser, const ENCODING *enc, XML_Bool haveMore, XML_Bool allowClosingDoctype, enum XML_Account account); static enum XML_Error processEntity(XML_Parser parser, ENTITY *entity, - XML_Bool betweenDecl, enum EntityType type); + bool betweenDecl, enum EntityType type); static enum XML_Error doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, const char *start, const char *end, const char **endPtr, @@ -569,7 +570,7 @@ static void reportDefault(XML_Parser parser, const ENCODING *enc, static const XML_Char *getContext(XML_Parser parser); static XML_Bool setContext(XML_Parser parser, const XML_Char *context); -static void FASTCALL normalizePublicId(XML_Char *s); +static void normalizePublicId(XML_Char *s); static DTD *dtdCreate(XML_Parser parser); /* do not call if m_parentParser != NULL */ @@ -579,34 +580,24 @@ static int dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, XML_Parser parser); static int copyEntityTable(XML_Parser oldParser, HASH_TABLE *newTable, STRING_POOL *newPool, const HASH_TABLE *oldTable); -static NAMED *lookup(XML_Parser parser, HASH_TABLE *table, KEY name, - size_t createSize); -static void FASTCALL hashTableInit(HASH_TABLE *table, XML_Parser parser); -static void FASTCALL hashTableClear(HASH_TABLE *table); -static void FASTCALL hashTableDestroy(HASH_TABLE *table); -static void FASTCALL hashTableIterInit(HASH_TABLE_ITER *iter, - const HASH_TABLE *table); -static NAMED *FASTCALL hashTableIterNext(HASH_TABLE_ITER *iter); -static void FASTCALL poolInit(STRING_POOL *pool, XML_Parser parser); -static void FASTCALL poolClear(STRING_POOL *pool); -static void FASTCALL poolDestroy(STRING_POOL *pool); +static void poolInit(STRING_POOL *pool, XML_Parser parser); +static void poolClear(STRING_POOL *pool); +static void poolDestroy(STRING_POOL *pool); static XML_Char *poolAppend(STRING_POOL *pool, const ENCODING *enc, const char *ptr, const char *end); static XML_Char *poolStoreString(STRING_POOL *pool, const ENCODING *enc, const char *ptr, const char *end); -static XML_Bool FASTCALL poolGrow(STRING_POOL *pool); -static bool FASTCALL poolGrowUntil(STRING_POOL *pool, size_t needed); -static const XML_Char *FASTCALL poolCopyString(STRING_POOL *pool, - const XML_Char *s); -static const XML_Char *FASTCALL poolCopyStringNoFinish(STRING_POOL *pool, - const XML_Char *s); +static XML_Bool poolGrow(STRING_POOL *pool); +static bool poolGrowUntil(STRING_POOL *pool, size_t needed); +static const XML_Char *poolCopyString(STRING_POOL *pool, const XML_Char *s); +static const XML_Char *poolCopyStringNoFinish(STRING_POOL *pool, + const XML_Char *s); static const XML_Char *poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n); -static const XML_Char *FASTCALL poolAppendString(STRING_POOL *pool, - const XML_Char *s); +static const XML_Char *poolAppendString(STRING_POOL *pool, const XML_Char *s); -static int FASTCALL nextScaffoldPart(XML_Parser parser); +static int nextScaffoldPart(XML_Parser parser); static XML_Content *build_model(XML_Parser parser); static ELEMENT_TYPE *getElementType(XML_Parser parser, const ENCODING *enc, const char *ptr, const char *end); @@ -755,6 +746,8 @@ struct XML_ParserStruct { void *m_unknownEncodingMem; void *m_unknownEncodingData; void *m_unknownEncodingHandlerData; + // Application callback invoked by callUnknownEncodingConvert. + int(XMLCALL *m_unknownEncodingConvert)(void *, const char *); void(XMLCALL *m_unknownEncodingRelease)(void *); PROLOG_STATE m_prologState; Processor *m_processor; @@ -1177,6 +1170,25 @@ isCalledFromInsideHandler(XML_Parser parser) { return parser->m_handlerCallDepth > 0; } +static void +callUnknownEncodingRelease(XML_Parser parser) { + beforeHandler(parser); + parser->m_unknownEncodingRelease(parser->m_unknownEncodingData); + afterHandler(parser); + parser->m_unknownEncodingRelease = NULL; + parser->m_unknownEncodingData = NULL; +} + +static int XMLCALL +callUnknownEncodingConvert(void *data, const char *p) { + XML_Parser parser = data; + beforeHandler(parser); + const int result + = parser->m_unknownEncodingConvert(parser->m_unknownEncodingData, p); + afterHandler(parser); + return result; +} + static enum XML_Error callProcessor(XML_Parser parser, const char *start, const char *end, const char **endPtr) { @@ -1524,6 +1536,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { parser->m_inheritedBindings = NULL; parser->m_nSpecifiedAtts = 0; parser->m_unknownEncodingMem = NULL; + parser->m_unknownEncodingConvert = NULL; parser->m_unknownEncodingRelease = NULL; parser->m_unknownEncodingData = NULL; parser->m_parsingStatus.parsing = XML_INITIALIZED; @@ -1553,7 +1566,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { } /* moves list of bindings to m_freeBindingList */ -static void FASTCALL +static void moveToFreeBindingList(XML_Parser parser, BINDING *bindings) { while (bindings) { BINDING *b = bindings; @@ -1604,7 +1617,7 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) { moveToFreeBindingList(parser, parser->m_inheritedBindings); FREE(parser, parser->m_unknownEncodingMem); if (parser->m_unknownEncodingRelease) - parser->m_unknownEncodingRelease(parser->m_unknownEncodingData); + callUnknownEncodingRelease(parser); poolClear(&parser->m_tempPool); poolClear(&parser->m_temp2Pool); FREE(parser, (void *)parser->m_protocolEncodingName); @@ -1826,7 +1839,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context, return parser; } -static void FASTCALL +static void destroyBindings(BINDING *bindings, XML_Parser parser) { for (;;) { BINDING *b = bindings; @@ -1915,7 +1928,7 @@ XML_ParserFree(XML_Parser parser) { FREE(parser, parser->m_nsAtts); FREE(parser, parser->m_unknownEncodingMem); if (parser->m_unknownEncodingRelease) - parser->m_unknownEncodingRelease(parser->m_unknownEncodingData); + callUnknownEncodingRelease(parser); FREE(parser, parser); } @@ -2225,9 +2238,10 @@ XML_SetParamEntityParsing(XML_Parser parser, #endif } -// DEPRECATED since Expat 2.8.0. -int XMLCALL -XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) { +/* The body of XML_SetHashSalt, so that Expat's own tests can reach it + without tripping the deprecation of the public function. */ +XML_NONTESTING_STATIC int +xmlSetHashSalt(XML_Parser parser, unsigned long hash_salt) { if (parser == NULL) return 0; @@ -2253,6 +2267,12 @@ XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) { return 1; } +// DEPRECATED since Expat 2.8.0. +int XMLCALL +XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) { + return xmlSetHashSalt(parser, hash_salt); +} + XML_Bool XMLCALL XML_SetHashSalt16Bytes(XML_Parser parser, const uint8_t entropy[16]) { if (parser == NULL) @@ -2739,7 +2759,7 @@ XML_GetCurrentLineNumber(XML_Parser parser) { parser->m_eventPtr, &parser->m_position); parser->m_positionPtr = parser->m_eventPtr; } - // NOTE: XML_Size is known to wrap around for >2 4iB content + // NOTE: XML_Size is known to wrap around for >4 GiB content // on 32bit machines and 64bit Windows, unless (non-default and // uncommon) XML_LARGE_SIZE is defined. // That's a bug and it only lives on because we cannot break @@ -2756,7 +2776,7 @@ XML_GetCurrentColumnNumber(XML_Parser parser) { parser->m_eventPtr, &parser->m_position); parser->m_positionPtr = parser->m_eventPtr; } - // NOTE: XML_Size is known to wrap around for >2 4iB content + // NOTE: XML_Size is known to wrap around for >4 GiB content // on 32bit machines and 64bit Windows, unless (non-default and // uncommon) XML_LARGE_SIZE is defined. // That's a bug and it only lives on because we cannot break @@ -3125,7 +3145,7 @@ storeRawNames(XML_Parser parser) { return XML_TRUE; } -static enum XML_Error PTRCALL +static enum XML_Error contentProcessor(XML_Parser parser, const char *start, const char *end, const char **endPtr) { enum XML_Error result = doContent( @@ -3139,7 +3159,7 @@ contentProcessor(XML_Parser parser, const char *start, const char *end, return result; } -static enum XML_Error PTRCALL +static enum XML_Error externalEntityInitProcessor(XML_Parser parser, const char *start, const char *end, const char **endPtr) { enum XML_Error result = initializeEncoding(parser); @@ -3149,7 +3169,7 @@ externalEntityInitProcessor(XML_Parser parser, const char *start, return externalEntityInitProcessor2(parser, start, end, endPtr); } -static enum XML_Error PTRCALL +static enum XML_Error externalEntityInitProcessor2(XML_Parser parser, const char *start, const char *end, const char **endPtr) { const char *next = start; /* XmlContentTok doesn't always set the last arg */ @@ -3194,7 +3214,7 @@ externalEntityInitProcessor2(XML_Parser parser, const char *start, return externalEntityInitProcessor3(parser, start, end, endPtr); } -static enum XML_Error PTRCALL +static enum XML_Error externalEntityInitProcessor3(XML_Parser parser, const char *start, const char *end, const char **endPtr) { int tok; @@ -3246,7 +3266,7 @@ externalEntityInitProcessor3(XML_Parser parser, const char *start, return externalEntityContentProcessor(parser, start, end, endPtr); } -static enum XML_Error PTRCALL +static enum XML_Error externalEntityContentProcessor(XML_Parser parser, const char *start, const char *end, const char **endPtr) { enum XML_Error result @@ -3410,9 +3430,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, return result; } else if (parser->m_externalEntityRefHandler) { const XML_Char *context; - entity->open = XML_TRUE; + entity->open = true; context = getContext(parser); - entity->open = XML_FALSE; + entity->open = false; if (! context) return XML_ERROR_NO_MEMORY; beforeHandler(parser); @@ -3837,8 +3857,8 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, sizeof(ELEMENT_TYPE)); if (! elementType) return XML_ERROR_NO_MEMORY; - if (! elementType->defaultAttsNames.parser) - hashTableInit(&(elementType->defaultAttsNames), parser); + if (! elementType->defaultAttForName.parser) + hashTableInit(&(elementType->defaultAttForName), parser); if (parser->m_ns && ! setElementTypePrefix(parser, elementType)) return XML_ERROR_NO_MEMORY; } @@ -3951,11 +3971,14 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, /* figure out whether declared as other than CDATA */ if (attId->maybeTokenized) { - for (size_t j = 0; j < nDefaultAtts; j++) { - if (attId == elementType->defaultAtts[j].id) { - isCdata = elementType->defaultAtts[j].isCdata; - break; - } + NAME_AND_DEFAULT_ATTRIBUTE *const nameAndDefaultAttribute + = (NAME_AND_DEFAULT_ATTRIBUTE *)lookup( + parser, &(elementType->defaultAttForName), attId->name, 0); + if (nameAndDefaultAttribute != NULL) { + assert(nameAndDefaultAttribute->attIndex < elementType->nDefaultAtts); + const DEFAULT_ATTRIBUTE *const att + = elementType->defaultAtts + nameAndDefaultAttribute->attIndex; + isCdata = att->isCdata; } } @@ -4046,8 +4069,8 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, unsigned int nsAttsSize = 1u << parser->m_nsAttsPower; unsigned char oldNsAttsPower = parser->m_nsAttsPower; /* size of hash table must be at least 2 * (# of prefixed attributes) */ - if ((nPrefixes << 1) - >> parser->m_nsAttsPower) { /* true for m_nsAttsPower = 0 */ + if (parser->m_nsAttsPower == 0 + || (nPrefixes >> (parser->m_nsAttsPower - 1))) { /* hash table size must also be a power of 2 and >= 8 */ while (nPrefixes >> parser->m_nsAttsPower++) ; @@ -4505,6 +4528,7 @@ addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, /* Detect and prevent integer overflow */ if (len > SIZE_MAX - EXPAND_SPARE || len + EXPAND_SPARE > SIZE_MAX / sizeof(XML_Char)) { + FREE(parser, b); return XML_ERROR_NO_MEMORY; } @@ -4542,7 +4566,7 @@ addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, /* The idea here is to avoid using stack for each CDATA section when the whole file is parsed with one call. */ -static enum XML_Error PTRCALL +static enum XML_Error cdataSectionProcessor(XML_Parser parser, const char *start, const char *end, const char **endPtr) { enum XML_Error result = doCdataSection( @@ -4708,7 +4732,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, /* The idea here is to avoid using stack for each IGNORE section when the whole file is parsed with one call. */ -static enum XML_Error PTRCALL +static enum XML_Error ignoreSectionProcessor(XML_Parser parser, const char *start, const char *end, const char **endPtr) { enum XML_Error result @@ -4946,30 +4970,39 @@ handleUnknownEncoding(XML_Parser parser, const XML_Char *encodingName) { const int status = parser->m_unknownEncodingHandler( parser->m_unknownEncodingHandlerData, encodingName, &info); afterHandler(parser); + + parser->m_unknownEncodingRelease = info.release; + parser->m_unknownEncodingData = info.data; + if (status) { ENCODING *enc; parser->m_unknownEncodingMem = MALLOC(parser, XmlSizeOfUnknownEncoding()); if (! parser->m_unknownEncodingMem) { - if (info.release) - info.release(info.data); + if (parser->m_unknownEncodingRelease) + callUnknownEncodingRelease(parser); + else + parser->m_unknownEncodingData = NULL; return XML_ERROR_NO_MEMORY; } + parser->m_unknownEncodingConvert = info.convert; enc = (parser->m_ns ? XmlInitUnknownEncodingNS : XmlInitUnknownEncoding)( - parser->m_unknownEncodingMem, info.map, info.convert, info.data); + parser->m_unknownEncodingMem, info.map, + info.convert ? callUnknownEncodingConvert : NULL, parser); if (enc) { - parser->m_unknownEncodingData = info.data; - parser->m_unknownEncodingRelease = info.release; parser->m_encoding = enc; return XML_ERROR_NONE; } + parser->m_unknownEncodingConvert = NULL; } - if (info.release != NULL) - info.release(info.data); + if (parser->m_unknownEncodingRelease != NULL) + callUnknownEncodingRelease(parser); + else + parser->m_unknownEncodingData = NULL; } return XML_ERROR_UNKNOWN_ENCODING; } -static enum XML_Error PTRCALL +static enum XML_Error prologInitProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { enum XML_Error result = initializeEncoding(parser); @@ -4981,7 +5014,7 @@ prologInitProcessor(XML_Parser parser, const char *s, const char *end, #ifdef XML_DTD -static enum XML_Error PTRCALL +static enum XML_Error externalParEntInitProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { enum XML_Error result = initializeEncoding(parser); @@ -4990,7 +5023,7 @@ externalParEntInitProcessor(XML_Parser parser, const char *s, const char *end, /* we know now that XML_Parse(Buffer) has been called, so we consider the external parameter entity read */ - parser->m_dtd->paramEntityRead = XML_TRUE; + parser->m_dtd->paramEntityRead = true; if (parser->m_prologState.inEntityValue) { parser->m_processor = entityValueInitProcessor; @@ -5001,7 +5034,7 @@ externalParEntInitProcessor(XML_Parser parser, const char *s, const char *end, } } -static enum XML_Error PTRCALL +static enum XML_Error entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { int tok; @@ -5085,7 +5118,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, } } -static enum XML_Error PTRCALL +static enum XML_Error externalParEntProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { const char *next = s; @@ -5131,7 +5164,7 @@ externalParEntProcessor(XML_Parser parser, const char *s, const char *end, XML_ACCOUNT_DIRECT); } -static enum XML_Error PTRCALL +static enum XML_Error entityValueProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { const char *start = s; @@ -5178,7 +5211,7 @@ entityValueProcessor(XML_Parser parser, const char *s, const char *end, #endif /* XML_DTD */ -static enum XML_Error PTRCALL +static enum XML_Error prologProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { const char *next = s; @@ -5424,7 +5457,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } if (parser->m_useForeignDTD) entity->base = parser->m_curBase; - dtd->paramEntityRead = XML_FALSE; + dtd->paramEntityRead = false; beforeHandler(parser); const int status = parser->m_externalEntityRefHandler( parser->m_externalEntityRefHandlerArg, 0, entity->base, @@ -5474,7 +5507,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, if (! entity) return XML_ERROR_NO_MEMORY; entity->base = parser->m_curBase; - dtd->paramEntityRead = XML_FALSE; + dtd->paramEntityRead = false; beforeHandler(parser); const int status = parser->m_externalEntityRefHandler( parser->m_externalEntityRefHandlerArg, 0, entity->base, @@ -5817,7 +5850,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } else { poolFinish(&dtd->pool); parser->m_declEntity->publicId = NULL; - parser->m_declEntity->is_param = XML_FALSE; + parser->m_declEntity->is_param = false; /* if we have a parent parser or are reading an internal parameter entity, then the entity declaration is not considered "internal" */ @@ -5847,7 +5880,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } else { poolFinish(&dtd->pool); parser->m_declEntity->publicId = NULL; - parser->m_declEntity->is_param = XML_TRUE; + parser->m_declEntity->is_param = true; /* if we have a parent parser or are reading an internal parameter entity, then the entity declaration is not considered "internal" */ @@ -6082,8 +6115,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, return XML_ERROR_RECURSIVE_ENTITY_REF; if (entity->textPtr) { enum XML_Error result; - XML_Bool betweenDecl - = (role == XML_ROLE_PARAM_ENTITY_REF ? XML_TRUE : XML_FALSE); + bool betweenDecl = (role == XML_ROLE_PARAM_ENTITY_REF); result = processEntity(parser, entity, betweenDecl, ENTITY_INTERNAL); if (result != XML_ERROR_NONE) return result; @@ -6091,8 +6123,8 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, break; } if (parser->m_externalEntityRefHandler) { - dtd->paramEntityRead = XML_FALSE; - entity->open = XML_TRUE; + dtd->paramEntityRead = false; + entity->open = true; entityTrackingOnOpen(parser, entity, __LINE__); beforeHandler(parser); const int status = parser->m_externalEntityRefHandler( @@ -6101,11 +6133,11 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, afterHandler(parser); if (! status) { entityTrackingOnClose(parser, entity, __LINE__); - entity->open = XML_FALSE; + entity->open = false; return XML_ERROR_EXTERNAL_ENTITY_HANDLING; } entityTrackingOnClose(parser, entity, __LINE__); - entity->open = XML_FALSE; + entity->open = false; handleDefault = XML_FALSE; if (! dtd->paramEntityRead) { dtd->keepProcessing = dtd->standalone; @@ -6135,7 +6167,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, return XML_ERROR_NO_MEMORY; dtd->scaffLevel = 0; dtd->scaffCount = 0; - dtd->in_eldecl = XML_TRUE; + dtd->in_eldecl = true; handleDefault = XML_FALSE; } break; @@ -6164,7 +6196,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, afterHandler(parser); handleDefault = XML_FALSE; } - dtd->in_eldecl = XML_FALSE; + dtd->in_eldecl = false; } break; @@ -6246,7 +6278,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, parser->m_handlerArg, parser->m_declElementType->name, model); afterHandler(parser); } - dtd->in_eldecl = XML_FALSE; + dtd->in_eldecl = false; dtd->contentStringLen = 0; } } @@ -6315,7 +6347,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, /* not reached */ } -static enum XML_Error PTRCALL +static enum XML_Error epilogProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { parser->m_processor = epilogProcessor; @@ -6394,7 +6426,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, } static enum XML_Error -processEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl, +processEntity(XML_Parser parser, ENTITY *entity, bool betweenDecl, enum EntityType type) { OPEN_INTERNAL_ENTITY *openEntity, **openEntityList; OPEN_INTERNAL_ENTITY **const freeEntityList = &parser->m_freeEntities; @@ -6429,8 +6461,8 @@ processEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl, if (! openEntity) return XML_ERROR_NO_MEMORY; } - entity->open = XML_TRUE; - entity->hasMore = XML_TRUE; + entity->open = true; + entity->hasMore = true; #if XML_GE == 1 entityTrackingOnOpen(parser, entity, __LINE__); #endif @@ -6452,7 +6484,7 @@ processEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl, return XML_ERROR_NONE; } -static enum XML_Error PTRCALL +static enum XML_Error internalEntityProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { UNUSED_P(s); @@ -6502,7 +6534,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, // Entity is complete. We cannot close it here since we need to first // process its possible inner entities (which are added to the // m_openInternalEntities during doProlog or doContent calls above) - entity->hasMore = XML_FALSE; + entity->hasMore = false; if (! entity->is_param && (openEntity->startTagLevel != parser->m_tagLevel)) { return XML_ERROR_ASYNC_ENTITY; @@ -6520,7 +6552,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, // to false. This means we can directly remove the head of // m_openInternalEntities assert(parser->m_openInternalEntities == openEntity); - entity->open = XML_FALSE; + entity->open = false; parser->m_openInternalEntities = parser->m_openInternalEntities->next; /* put openEntity back in list of free instances */ @@ -6534,7 +6566,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, return XML_ERROR_NONE; } -static enum XML_Error PTRCALL +static enum XML_Error errorProcessor(XML_Parser parser, const char *s, const char *end, const char **nextPtr) { UNUSED_P(s); @@ -6585,7 +6617,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, // Entity is complete. We cannot close it here since we need to first // process its possible inner entities (which are added to the // m_openAttributeEntities during appendAttributeValue) - entity->hasMore = XML_FALSE; + entity->hasMore = false; continue; } // End of entity processing, "if" block skips the rest @@ -6598,7 +6630,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, // with hasMore set to false. This means we can directly remove the head // of m_openAttributeEntities assert(parser->m_openAttributeEntities == openEntity); - entity->open = XML_FALSE; + entity->open = false; parser->m_openAttributeEntities = parser->m_openAttributeEntities->next; /* put openEntity back in list of free instances */ @@ -6893,8 +6925,8 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, } if (entity->systemId) { if (parser->m_externalEntityRefHandler) { - dtd->paramEntityRead = XML_FALSE; - entity->open = XML_TRUE; + dtd->paramEntityRead = false; + entity->open = true; entityTrackingOnOpen(parser, entity, __LINE__); beforeHandler(parser); const int status = parser->m_externalEntityRefHandler( @@ -6903,12 +6935,12 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, afterHandler(parser); if (! status) { entityTrackingOnClose(parser, entity, __LINE__); - entity->open = XML_FALSE; + entity->open = false; result = XML_ERROR_EXTERNAL_ENTITY_HANDLING; goto endEntityValue; } entityTrackingOnClose(parser, entity, __LINE__); - entity->open = XML_FALSE; + entity->open = false; if (! dtd->paramEntityRead) dtd->keepProcessing = dtd->standalone; } else @@ -7045,7 +7077,7 @@ callStoreEntityValue(XML_Parser parser, const ENCODING *enc, // Entity is complete. We cannot close it here since we need to first // process its possible inner entities (which are added to the // m_openValueEntities during storeEntityValue) - entity->hasMore = XML_FALSE; + entity->hasMore = false; continue; } // End of entity processing, "if" block skips the rest @@ -7058,7 +7090,7 @@ callStoreEntityValue(XML_Parser parser, const ENCODING *enc, // with hasMore set to false. This means we can directly remove the head // of m_openValueEntities assert(parser->m_openValueEntities == openEntity); - entity->open = XML_FALSE; + entity->open = false; parser->m_openValueEntities = parser->m_openValueEntities->next; /* put openEntity back in list of free instances */ @@ -7107,7 +7139,7 @@ storeSelfEntityValue(XML_Parser parser, ENTITY *entity) { #endif /* XML_GE == 0 */ -static void FASTCALL +static void normalizeLines(XML_Char *s) { XML_Char *p; for (;; s++) { @@ -7239,7 +7271,7 @@ defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata, /* The handling of default attributes gets messed up if we have a default which duplicates a non-default. */ NAMED *const nameFound - = lookup(parser, &(type->defaultAttsNames), attId->name, 0); + = lookup(parser, &(type->defaultAttForName), attId->name, 0); if (nameFound) return 1; if (isId && ! type->idAtt && ! attId->xmlns) @@ -7273,13 +7305,26 @@ defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata, att->value = value; att->isCdata = isCdata; if (! isCdata) - attId->maybeTokenized = XML_TRUE; + attId->maybeTokenized = true; - NAMED *const nameAddedOrFound - = lookup(parser, &(type->defaultAttsNames), attId->name, sizeof(NAMED)); - if (! nameAddedOrFound) + NAME_AND_DEFAULT_ATTRIBUTE *const nameAndDefaultAttribute + = (NAME_AND_DEFAULT_ATTRIBUTE *)lookup( + parser, &(type->defaultAttForName), attId->name, + sizeof(NAME_AND_DEFAULT_ATTRIBUTE)); + if (! nameAndDefaultAttribute) return 0; + assert(nameAndDefaultAttribute->name == attId->name); + + // NOTE: The XML 1.0r4 spec says: + // "When more than one definition is provided for the same attribute of a + // given element type, the first declaration is binding and later + // declarations are ignored." + if (! nameAndDefaultAttribute->initialized) { + nameAndDefaultAttribute->attIndex = type->nDefaultAtts; + nameAndDefaultAttribute->initialized = true; + } + type->nDefaultAtts += 1; return 1; } @@ -7345,7 +7390,7 @@ getAttributeId(XML_Parser parser, const ENCODING *enc, const char *start, else id->prefix = (PREFIX *)lookup(parser, &dtd->prefixes, name + 6, sizeof(PREFIX)); - id->xmlns = XML_TRUE; + id->xmlns = true; } else { int i; for (i = 0; name[i]; i++) { @@ -7480,7 +7525,7 @@ setContext(XML_Parser parser, const XML_Char *context) { e = (ENTITY *)lookup(parser, &dtd->generalEntities, poolStart(&parser->m_tempPool), 0); if (e) - e->open = XML_TRUE; + e->open = true; if (*s != XML_T('\0')) s++; context = s; @@ -7535,7 +7580,7 @@ setContext(XML_Parser parser, const XML_Char *context) { return XML_TRUE; } -static void FASTCALL +static void normalizePublicId(XML_Char *publicId) { XML_Char *p = publicId; XML_Char *s; @@ -7568,13 +7613,13 @@ dtdCreate(XML_Parser parser) { hashTableInit(&(p->attributeIds), parser); hashTableInit(&(p->prefixes), parser); #ifdef XML_DTD - p->paramEntityRead = XML_FALSE; + p->paramEntityRead = false; hashTableInit(&(p->paramEntities), parser); #endif /* XML_DTD */ p->defaultPrefix.name = NULL; p->defaultPrefix.binding = NULL; - p->in_eldecl = XML_FALSE; + p->in_eldecl = false; p->scaffIndex = NULL; p->scaffIndexSize = 0; p->scaffold = NULL; @@ -7597,12 +7642,12 @@ dtdReset(DTD *p, XML_Parser parser) { ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter); if (! e) break; - hashTableDestroy(&(e->defaultAttsNames)); + hashTableDestroy(&(e->defaultAttForName)); FREE(parser, e->defaultAtts); } hashTableClear(&(p->generalEntities)); #ifdef XML_DTD - p->paramEntityRead = XML_FALSE; + p->paramEntityRead = false; hashTableClear(&(p->paramEntities)); #endif /* XML_DTD */ hashTableClear(&(p->elementTypes)); @@ -7613,7 +7658,7 @@ dtdReset(DTD *p, XML_Parser parser) { p->defaultPrefix.name = NULL; p->defaultPrefix.binding = NULL; - p->in_eldecl = XML_FALSE; + p->in_eldecl = false; FREE(parser, p->scaffIndex); p->scaffIndex = NULL; @@ -7639,7 +7684,7 @@ dtdDestroy(DTD *p, XML_Bool isDocEntity, XML_Parser parser) { ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter); if (! e) break; - hashTableDestroy(&(e->defaultAttsNames)); + hashTableDestroy(&(e->defaultAttForName)); FREE(parser, e->defaultAtts); } hashTableDestroy(&(p->generalEntities)); @@ -7732,8 +7777,8 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, if (! newE) return 0; - if (! newE->defaultAttsNames.parser) - hashTableInit(&(newE->defaultAttsNames), parser); + if (! newE->defaultAttForName.parser) + hashTableInit(&(newE->defaultAttForName), parser); if (oldE->nDefaultAtts) { /* Detect and prevent integer overflow. */ @@ -7766,11 +7811,22 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, } else newE->defaultAtts[i].value = NULL; - NAMED *const nameAddedOrFound = lookup(parser, &(newE->defaultAttsNames), - attributeName, sizeof(NAMED)); - if (! nameAddedOrFound) { + NAME_AND_DEFAULT_ATTRIBUTE *const nameAndDefaultAttribute + = (NAME_AND_DEFAULT_ATTRIBUTE *)lookup( + parser, &(newE->defaultAttForName), attributeName, + sizeof(NAME_AND_DEFAULT_ATTRIBUTE)); + if (! nameAndDefaultAttribute) { return 0; } + + // NOTE: The XML 1.0r4 spec says: + // "When more than one definition is provided for the same attribute of a + // given element type, the first declaration is binding and later + // declarations are ignored." + if (! nameAndDefaultAttribute->initialized) { + nameAndDefaultAttribute->attIndex = i; + nameAndDefaultAttribute->initialized = true; + } } } @@ -7867,23 +7923,27 @@ copyEntityTable(XML_Parser oldParser, HASH_TABLE *newTable, #define INIT_POWER 6 -static XML_Bool FASTCALL -keyeq(KEY s1, KEY s2) { +// Compares two strings `s1` and `s2` whereas: +// - `s2` is zero-terminated but +// - `s1` is made up of exactly (not just up to) `s1len` non-zero characters. +XML_NONTESTING_STATIC XML_Bool +keyeq(KEY s1, size_t s1len, KEY s2) { #ifdef XML_UNICODE # ifdef XML_UNICODE_WCHAR_T - return (wcscmp(s1, s2) == 0) ? XML_TRUE : XML_FALSE; + return (wcsncmp(s1, s2, s1len) == 0 && s2[s1len] == L'\0') ? XML_TRUE + : XML_FALSE; # else - for (; *s1 == *s2; s1++, s2++) - if (*s1 == 0) - return XML_TRUE; - return XML_FALSE; + for (; s1len > 0 && *s1 == *s2; s1len--, s1++, s2++) + ; /* no loop body! */ + return ((s1len == 0) && (*s2 == 0)) ? XML_TRUE : XML_FALSE; # endif #else - return (strcmp(s1, s2) == 0) ? XML_TRUE : XML_FALSE; + return (strncmp(s1, s2, s1len) == 0 && s2[s1len] == '\0') ? XML_TRUE + : XML_FALSE; #endif } -static size_t +XML_NONTESTING_STATIC size_t keylen(KEY s) { return xcslen(s); } @@ -7896,19 +7956,39 @@ copy_salt_to_sipkey(XML_Parser parser, struct sipkey *key) { *key = rootParser->m_hash_secret_salt_128; } -static unsigned long FASTCALL -hash(XML_Parser parser, KEY s) { +static unsigned long +hash(XML_Parser parser, KEY s, size_t keyLen) { struct siphash state; struct sipkey key; (void)sip24_valid; copy_salt_to_sipkey(parser, &key); sip24_init(&state, &key); - sip24_update(&state, s, keylen(s) * sizeof(XML_Char)); + sip24_update(&state, s, keyLen * sizeof(XML_Char)); return (unsigned long)sip24_final(&state); } -static NAMED * -lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) { +// Function `lookupWithLength` can be used to either… +// +// a) check whether an element with key `name` exists in the given hash table +// (read-only mode where `createSize == 0`) or +// +// b) check whether an element with key `name` exists in the given hash table +// *and* insert it if missing (i.e. read-write mode where `createSize != 0`. +// +// When inserting, a block of `createSize` number of bytes will be allocated +// and set to zero, and the resulting block of memory will be considered +// to start with a `NAMED` structure, and `->name = name;` is performed. +// The fact that all other bytes in the structure are initially zero can +// be used to tell cases "existed and found" and "newly inserted" apart +// with the structure returned. +// +// NOTE: Read-only lookup does not need zero-terminated keys but +// read-write mode does, because keys can be re-hashed later and the +// hash table does not store key length information. +// +XML_NONTESTING_STATIC NAMED * +lookupWithLength(XML_Parser parser, HASH_TABLE *table, KEY name, size_t nameLen, + size_t createSize) { size_t i; if (table->size == 0) { size_t tsize; @@ -7924,14 +8004,14 @@ lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) { return NULL; } memset(table->v, 0, tsize); - i = hash(parser, name) & ((unsigned long)table->size - 1); + i = hash(parser, name, nameLen) & ((unsigned long)table->size - 1); } else { - unsigned long h = hash(parser, name); + unsigned long h = hash(parser, name, nameLen); unsigned long mask = (unsigned long)table->size - 1; unsigned char step = 0; i = h & mask; while (table->v[i]) { - if (keyeq(name, table->v[i]->name)) + if (keyeq(name, nameLen, table->v[i]->name)) return table->v[i]; if (! step) step = PROBE_STEP(h, mask, table->power); @@ -7964,7 +8044,8 @@ lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) { memset(newV, 0, tsize); for (i = 0; i < table->size; i++) if (table->v[i]) { - unsigned long newHash = hash(parser, table->v[i]->name); + KEY const key = table->v[i]->name; + unsigned long newHash = hash(parser, key, keylen(key)); size_t j = newHash & newMask; step = 0; while (newV[j]) { @@ -7987,16 +8068,37 @@ lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) { } } } + assert(createSize >= sizeof(NAMED)); table->v[i] = MALLOC(table->parser, createSize); if (! table->v[i]) return NULL; memset(table->v[i], 0, createSize); - table->v[i]->name = name; + table->v[i]->name = name; // NOTE: This requires and assumes zero termination! (table->used)++; return table->v[i]; } -static void FASTCALL +// Function `lookup` can be used to either… +// +// a) check whether an element with key `name` exists in the given hash table +// (read-only mode where `createSize == 0`) or +// +// b) check whether an element with key `name` exists in the given hash table +// *and* insert it if missing (i.e. read-write mode where `createSize != 0`. +// +// When inserting, a block of `createSize` number of bytes will be allocated +// and set to zero, and the resulting block of memory will be considered +// to start with a `NAMED` structure, and `->name = name;` is performed. +// The fact that all other bytes in the structure are initially zero can +// be used to tell cases "existed and found" and "newly inserted" apart +// with the structure returned. +// +XML_NONTESTING_STATIC NAMED * +lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) { + return lookupWithLength(parser, table, name, keylen(name), createSize); +} + +XML_NONTESTING_STATIC void hashTableClear(HASH_TABLE *table) { size_t i; for (i = 0; i < table->size; i++) { @@ -8006,7 +8108,7 @@ hashTableClear(HASH_TABLE *table) { table->used = 0; } -static void FASTCALL +XML_NONTESTING_STATIC void hashTableDestroy(HASH_TABLE *table) { size_t i; for (i = 0; i < table->size; i++) @@ -8014,7 +8116,7 @@ hashTableDestroy(HASH_TABLE *table) { FREE(table->parser, table->v); } -static void FASTCALL +XML_NONTESTING_STATIC void hashTableInit(HASH_TABLE *p, XML_Parser parser) { p->power = 0; p->size = 0; @@ -8023,13 +8125,13 @@ hashTableInit(HASH_TABLE *p, XML_Parser parser) { p->parser = parser; } -static void FASTCALL +XML_NONTESTING_STATIC void hashTableIterInit(HASH_TABLE_ITER *iter, const HASH_TABLE *table) { iter->p = table->v; iter->end = iter->p ? iter->p + table->size : NULL; } -static NAMED *FASTCALL +XML_NONTESTING_STATIC NAMED * hashTableIterNext(HASH_TABLE_ITER *iter) { while (iter->p != iter->end) { NAMED *tem = *(iter->p)++; @@ -8039,7 +8141,7 @@ hashTableIterNext(HASH_TABLE_ITER *iter) { return NULL; } -static void FASTCALL +static void poolInit(STRING_POOL *pool, XML_Parser parser) { pool->blocks = NULL; pool->freeBlocks = NULL; @@ -8049,7 +8151,7 @@ poolInit(STRING_POOL *pool, XML_Parser parser) { pool->parser = parser; } -static void FASTCALL +static void poolClear(STRING_POOL *pool) { if (! pool->freeBlocks) pool->freeBlocks = pool->blocks; @@ -8068,7 +8170,7 @@ poolClear(STRING_POOL *pool) { pool->end = NULL; } -static void FASTCALL +static void poolDestroy(STRING_POOL *pool) { BLOCK *p = pool->blocks; while (p) { @@ -8101,7 +8203,7 @@ poolAppend(STRING_POOL *pool, const ENCODING *enc, const char *ptr, return pool->start; } -static const XML_Char *FASTCALL +static const XML_Char * poolCopyString(STRING_POOL *pool, const XML_Char *s) { if (! poolAppendChars(pool, s, xcslen(s) + /*null terminator*/ 1)) return NULL; @@ -8112,7 +8214,7 @@ poolCopyString(STRING_POOL *pool, const XML_Char *s) { // A version of `poolCopyString` that does not call `poolFinish` // and reverts any partial advancement upon failure. -static const XML_Char *FASTCALL +static const XML_Char * poolCopyStringNoFinish(STRING_POOL *pool, const XML_Char *s) { const XML_Char *const original = s; do { @@ -8150,7 +8252,7 @@ poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n) { return s; } -static const XML_Char *FASTCALL +static const XML_Char * poolAppendString(STRING_POOL *pool, const XML_Char *s) { if (! poolAppendChars(pool, s, xcslen(s))) return NULL; @@ -8195,7 +8297,7 @@ poolBytesToAllocateFor(int blockSize) { } } -static XML_Bool FASTCALL +static XML_Bool poolGrow(STRING_POOL *pool) { if (pool->freeBlocks) { if (pool->start == NULL) { @@ -8300,7 +8402,7 @@ poolGrow(STRING_POOL *pool) { return XML_TRUE; } -static bool FASTCALL +static bool poolGrowUntil(STRING_POOL *pool, size_t needed) { for (;;) { const size_t available = pool->end - pool->ptr; @@ -8313,7 +8415,7 @@ poolGrowUntil(STRING_POOL *pool, size_t needed) { } } -static int FASTCALL +static int nextScaffoldPart(XML_Parser parser) { DTD *const dtd = parser->m_dtd; /* save one level of indirection */ CONTENT_SCAFFOLD *me; @@ -8535,8 +8637,8 @@ getElementType(XML_Parser parser, const ENCODING *enc, const char *ptr, sizeof(ELEMENT_TYPE)); if (! ret) return NULL; - if (! ret->defaultAttsNames.parser) - hashTableInit(&(ret->defaultAttsNames), getRootParserOf(parser, NULL)); + if (! ret->defaultAttForName.parser) + hashTableInit(&(ret->defaultAttForName), getRootParserOf(parser, NULL)); if (ret->name != name) poolDiscard(&dtd->pool); else { diff --git a/lib/xmlrole.c b/lib/xmlrole.c index 8f6bedda7165..dbf4a2d1b8f7 100644 --- a/lib/xmlrole.c +++ b/lib/xmlrole.c @@ -12,7 +12,7 @@ Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net> Copyright (c) 2002-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2005-2009 Steven Solie <steven@solie.ca> - Copyright (c) 2016-2023 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2019 David Loffredo <loffredo@steptools.com> Copyright (c) 2021 Donghee Na <donghee.na@python.org> @@ -121,9 +121,8 @@ static const char KW_SYSTEM[] # define setTopLevel(state) ((state)->handler = internalSubset) #endif /* not XML_DTD */ -typedef int PTRCALL PROLOG_HANDLER(PROLOG_STATE *state, int tok, - const char *ptr, const char *end, - const ENCODING *enc); +typedef int PROLOG_HANDLER(PROLOG_STATE *state, int tok, const char *ptr, + const char *end, const ENCODING *enc); static PROLOG_HANDLER prolog0, prolog1, prolog2, doctype0, doctype1, doctype2, doctype3, doctype4, doctype5, internalSubset, entity0, entity1, entity2, @@ -137,9 +136,9 @@ static PROLOG_HANDLER prolog0, prolog1, prolog2, doctype0, doctype1, doctype2, #endif /* XML_DTD */ declClose, error; -static int FASTCALL common(PROLOG_STATE *state, int tok); +static int common(PROLOG_STATE *state, int tok); -static int PTRCALL +static int prolog0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -170,7 +169,7 @@ prolog0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int prolog1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -202,7 +201,7 @@ prolog1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int prolog2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -222,7 +221,7 @@ prolog2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int doctype0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -239,7 +238,7 @@ doctype0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int doctype1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -265,7 +264,7 @@ doctype1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int doctype2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -281,7 +280,7 @@ doctype2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int doctype3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -297,7 +296,7 @@ doctype3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int doctype4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -316,7 +315,7 @@ doctype4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int doctype5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -332,7 +331,7 @@ doctype5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int internalSubset(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -377,7 +376,7 @@ internalSubset(PROLOG_STATE *state, int tok, const char *ptr, const char *end, #ifdef XML_DTD -static int PTRCALL +static int externalSubset0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { state->handler = externalSubset1; @@ -386,7 +385,7 @@ externalSubset0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return externalSubset1(state, tok, ptr, end, enc); } -static int PTRCALL +static int externalSubset1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -414,7 +413,7 @@ externalSubset1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, #endif /* XML_DTD */ -static int PTRCALL +static int entity0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -433,7 +432,7 @@ entity0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -449,7 +448,7 @@ entity1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -473,7 +472,7 @@ entity2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -489,7 +488,7 @@ entity3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -505,7 +504,7 @@ entity4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -524,7 +523,7 @@ entity5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity6(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -541,7 +540,7 @@ entity6(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity7(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -565,7 +564,7 @@ entity7(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity8(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -581,7 +580,7 @@ entity8(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity9(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -597,7 +596,7 @@ entity9(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int entity10(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -613,7 +612,7 @@ entity10(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int notation0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -629,7 +628,7 @@ notation0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int notation1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -649,7 +648,7 @@ notation1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int notation2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -665,7 +664,7 @@ notation2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int notation3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -682,7 +681,7 @@ notation3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int notation4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -702,7 +701,7 @@ notation4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -719,7 +718,7 @@ attlist0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -739,7 +738,7 @@ attlist1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -769,7 +768,7 @@ attlist2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -787,7 +786,7 @@ attlist3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -806,7 +805,7 @@ attlist4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -822,7 +821,7 @@ attlist5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist6(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -838,7 +837,7 @@ attlist6(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist7(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -858,7 +857,7 @@ attlist7(PROLOG_STATE *state, int tok, const char *ptr, const char *end, } /* default value */ -static int PTRCALL +static int attlist8(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -888,7 +887,7 @@ attlist8(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int attlist9(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -904,7 +903,7 @@ attlist9(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -921,7 +920,7 @@ element0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -947,7 +946,7 @@ element1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -981,7 +980,7 @@ element2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1005,7 +1004,7 @@ element3(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1022,7 +1021,7 @@ element4(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1042,7 +1041,7 @@ element5(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element6(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1071,7 +1070,7 @@ element6(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int element7(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1120,7 +1119,7 @@ element7(PROLOG_STATE *state, int tok, const char *ptr, const char *end, #ifdef XML_DTD -static int PTRCALL +static int condSect0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { switch (tok) { @@ -1140,7 +1139,7 @@ condSect0(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int condSect1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1157,7 +1156,7 @@ condSect1(PROLOG_STATE *state, int tok, const char *ptr, const char *end, return common(state, tok); } -static int PTRCALL +static int condSect2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1175,7 +1174,7 @@ condSect2(PROLOG_STATE *state, int tok, const char *ptr, const char *end, #endif /* XML_DTD */ -static int PTRCALL +static int declClose(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(ptr); @@ -1211,7 +1210,7 @@ declClose(PROLOG_STATE *state, int tok, const char *ptr, const char *end, * * LCOV_EXCL_START */ -static int PTRCALL +static int error(PROLOG_STATE *state, int tok, const char *ptr, const char *end, const ENCODING *enc) { UNUSED_P(state); @@ -1223,7 +1222,7 @@ error(PROLOG_STATE *state, int tok, const char *ptr, const char *end, } /* LCOV_EXCL_STOP */ -static int FASTCALL +static int common(PROLOG_STATE *state, int tok) { #ifdef XML_DTD if (! state->documentEntity && tok == XML_TOK_PARAM_ENTITY_REF) diff --git a/lib/xmlrole.h b/lib/xmlrole.h index 903a6951f434..f4aba4079f5c 100644 --- a/lib/xmlrole.h +++ b/lib/xmlrole.h @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net> Copyright (c) 2002 Karl Waclawek <karl@waclawek.net> Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> - Copyright (c) 2017-2025 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2017-2026 Sebastian Pipping <sebastian@pipping.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -40,10 +40,6 @@ # include "xmltok.h" -# ifdef __cplusplus -extern "C" { -# endif - enum { XML_ROLE_ERROR = -1, XML_ROLE_NONE = 0, @@ -112,8 +108,8 @@ enum { }; typedef struct prolog_state { - int(PTRCALL *handler)(struct prolog_state *state, int tok, const char *ptr, - const char *end, const ENCODING *enc); + int (*handler)(struct prolog_state *state, int tok, const char *ptr, + const char *end, const ENCODING *enc); unsigned level; int role_none; # ifdef XML_DTD @@ -131,8 +127,4 @@ void XmlPrologStateInitExternalEntity(PROLOG_STATE *state); # define XmlTokenRole(state, tok, ptr, end, enc) \ (((state)->handler)(state, tok, ptr, end, enc)) -# ifdef __cplusplus -} -# endif - #endif /* not XmlRole_INCLUDED */ diff --git a/lib/xmltok.c b/lib/xmltok.c index d66af6045f07..a3bf5e0e95b0 100644 --- a/lib/xmltok.c +++ b/lib/xmltok.c @@ -27,6 +27,7 @@ Copyright (c) 2025 Alfonso Gregory <gfunni234@gmail.com> Copyright (c) 2026 Nick Begg <nick@stunttruck.net> Copyright (c) 2026 Kartik Kenchi <netliomax25@gmail.com> + Copyright (c) 2026 Afonso Januário <afonso-januario@hotmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -136,20 +137,20 @@ : ((p)[1] & 0x80) == 0 \ || ((*p) == 0xF4 ? (p)[1] > 0x8F : ((p)[1] & 0xC0) == 0xC0))) -static int PTRFASTCALL +static int isNever(const ENCODING *enc, const char *p) { UNUSED_P(enc); UNUSED_P(p); return 0; } -static int PTRFASTCALL +static int utf8_isName2(const ENCODING *enc, const char *p) { UNUSED_P(enc); return UTF8_GET_NAMING2(namePages, (const unsigned char *)p); } -static int PTRFASTCALL +static int utf8_isName3(const ENCODING *enc, const char *p) { UNUSED_P(enc); return UTF8_GET_NAMING3(namePages, (const unsigned char *)p); @@ -157,13 +158,13 @@ utf8_isName3(const ENCODING *enc, const char *p) { #define utf8_isName4 isNever -static int PTRFASTCALL +static int utf8_isNmstrt2(const ENCODING *enc, const char *p) { UNUSED_P(enc); return UTF8_GET_NAMING2(nmstrtPages, (const unsigned char *)p); } -static int PTRFASTCALL +static int utf8_isNmstrt3(const ENCODING *enc, const char *p) { UNUSED_P(enc); return UTF8_GET_NAMING3(nmstrtPages, (const unsigned char *)p); @@ -171,19 +172,19 @@ utf8_isNmstrt3(const ENCODING *enc, const char *p) { #define utf8_isNmstrt4 isNever -static int PTRFASTCALL +static int utf8_isInvalid2(const ENCODING *enc, const char *p) { UNUSED_P(enc); return UTF8_INVALID2((const unsigned char *)p); } -static int PTRFASTCALL +static int utf8_isInvalid3(const ENCODING *enc, const char *p) { UNUSED_P(enc); return UTF8_INVALID3((const unsigned char *)p); } -static int PTRFASTCALL +static int utf8_isInvalid4(const ENCODING *enc, const char *p) { UNUSED_P(enc); return UTF8_INVALID4((const unsigned char *)p); @@ -193,21 +194,21 @@ struct normal_encoding { ENCODING enc; unsigned char type[256]; #ifdef XML_MIN_SIZE - int(PTRFASTCALL *byteType)(const ENCODING *, const char *); - int(PTRFASTCALL *isNameMin)(const ENCODING *, const char *); - int(PTRFASTCALL *isNmstrtMin)(const ENCODING *, const char *); - int(PTRFASTCALL *byteToAscii)(const ENCODING *, const char *); - int(PTRCALL *charMatches)(const ENCODING *, const char *, int); + int (*byteType)(const ENCODING *, const char *); + int (*isNameMin)(const ENCODING *, const char *); + int (*isNmstrtMin)(const ENCODING *, const char *); + int (*byteToAscii)(const ENCODING *, const char *); + int (*charMatches)(const ENCODING *, const char *, int); #endif /* XML_MIN_SIZE */ - int(PTRFASTCALL *isName2)(const ENCODING *, const char *); - int(PTRFASTCALL *isName3)(const ENCODING *, const char *); - int(PTRFASTCALL *isName4)(const ENCODING *, const char *); - int(PTRFASTCALL *isNmstrt2)(const ENCODING *, const char *); - int(PTRFASTCALL *isNmstrt3)(const ENCODING *, const char *); - int(PTRFASTCALL *isNmstrt4)(const ENCODING *, const char *); - int(PTRFASTCALL *isInvalid2)(const ENCODING *, const char *); - int(PTRFASTCALL *isInvalid3)(const ENCODING *, const char *); - int(PTRFASTCALL *isInvalid4)(const ENCODING *, const char *); + int (*isName2)(const ENCODING *, const char *); + int (*isName3)(const ENCODING *, const char *); + int (*isName4)(const ENCODING *, const char *); + int (*isNmstrt2)(const ENCODING *, const char *); + int (*isNmstrt3)(const ENCODING *, const char *); + int (*isNmstrt4)(const ENCODING *, const char *); + int (*isInvalid2)(const ENCODING *, const char *); + int (*isInvalid3)(const ENCODING *, const char *); + int (*isInvalid4)(const ENCODING *, const char *); }; #define AS_NORMAL_ENCODING(enc) ((const struct normal_encoding *)(enc)) @@ -232,7 +233,20 @@ struct normal_encoding { /* isNmstrt2 */ NULL, /* isNmstrt3 */ NULL, /* isNmstrt4 */ NULL, \ /* isInvalid2 */ NULL, /* isInvalid3 */ NULL, /* isInvalid4 */ NULL -static int FASTCALL checkCharRefNumber(int result); +/* Like NULL_VTABLE but with a real isInvalid4 so the UTF-16 encodings reject a + high surrogate that is not followed by a low surrogate. Only needed for the + XML_MIN_SIZE build, where the shared tokenizer dispatches through the vtable; + the regular build inlines the same check via IS_INVALID_CHAR. */ +#ifdef XML_MIN_SIZE +# define UTF16_NULL_VTABLE(E) \ + /* isName2 */ NULL, /* isName3 */ NULL, /* isName4 */ NULL, \ + /* isNmstrt2 */ NULL, /* isNmstrt3 */ NULL, /* isNmstrt4 */ NULL, \ + /* isInvalid2 */ NULL, /* isInvalid3 */ NULL, E##isInvalid4 +#else +# define UTF16_NULL_VTABLE(E) NULL_VTABLE +#endif + +static int checkCharRefNumber(int result); #include "xmltok_impl.h" #include "ascii.h" @@ -253,7 +267,7 @@ static int FASTCALL checkCharRefNumber(int result); (((const struct normal_encoding *)(enc))->type[(unsigned char)*(p)]) #ifdef XML_MIN_SIZE -static int PTRFASTCALL +static int sb_byteType(const ENCODING *enc, const char *p) { return SB_BYTE_TYPE(enc, p); } @@ -264,7 +278,7 @@ sb_byteType(const ENCODING *enc, const char *p) { #ifdef XML_MIN_SIZE # define BYTE_TO_ASCII(enc, p) (AS_NORMAL_ENCODING(enc)->byteToAscii(enc, p)) -static int PTRFASTCALL +static int sb_byteToAscii(const ENCODING *enc, const char *p) { UNUSED_P(enc); return *p; @@ -297,7 +311,7 @@ sb_byteToAscii(const ENCODING *enc, const char *p) { #ifdef XML_MIN_SIZE # define CHAR_MATCHES(enc, p, c) \ (AS_NORMAL_ENCODING(enc)->charMatches(enc, p, c)) -static int PTRCALL +static int sb_charMatches(const ENCODING *enc, const char *p, int c) { UNUSED_P(enc); return *p == c; @@ -368,7 +382,7 @@ _INTERNAL_trim_to_complete_utf8_characters(const char *from, *fromLimRef = fromLim; } -static enum XML_Convert_Result PTRCALL +static enum XML_Convert_Result utf8_toUtf8(const ENCODING *enc, const char **fromP, const char *fromLim, char **toP, const char *toLim) { bool input_incomplete = false; @@ -407,7 +421,7 @@ utf8_toUtf8(const ENCODING *enc, const char **fromP, const char *fromLim, return XML_CONVERT_COMPLETED; } -static enum XML_Convert_Result PTRCALL +static enum XML_Convert_Result utf8_toUtf16(const ENCODING *enc, const char **fromP, const char *fromLim, unsigned short **toP, const unsigned short *toLim) { enum XML_Convert_Result res = XML_CONVERT_COMPLETED; @@ -505,7 +519,7 @@ static const struct normal_encoding internal_utf8_encoding }, STANDARD_VTABLE(sb_) NORMAL_VTABLE(utf8_)}; -static enum XML_Convert_Result PTRCALL +static enum XML_Convert_Result latin1_toUtf8(const ENCODING *enc, const char **fromP, const char *fromLim, char **toP, const char *toLim) { UNUSED_P(enc); @@ -528,7 +542,7 @@ latin1_toUtf8(const ENCODING *enc, const char **fromP, const char *fromLim, } } -static enum XML_Convert_Result PTRCALL +static enum XML_Convert_Result latin1_toUtf16(const ENCODING *enc, const char **fromP, const char *fromLim, unsigned short **toP, const unsigned short *toLim) { UNUSED_P(enc); @@ -563,7 +577,7 @@ static const struct normal_encoding latin1_encoding }, STANDARD_VTABLE(sb_) NULL_VTABLE}; -static enum XML_Convert_Result PTRCALL +static enum XML_Convert_Result ascii_toUtf8(const ENCODING *enc, const char **fromP, const char *fromLim, char **toP, const char *toLim) { UNUSED_P(enc); @@ -598,7 +612,7 @@ static const struct normal_encoding ascii_encoding }, STANDARD_VTABLE(sb_) NULL_VTABLE}; -static int PTRFASTCALL +static int unicode_byte_type(char hi, char lo) { switch ((unsigned char)hi) { /* 0xD800-0xDBFF first 16-bit code unit or high surrogate (W1) */ @@ -625,7 +639,7 @@ unicode_byte_type(char hi, char lo) { } #define DEFINE_UTF16_TO_UTF8(E) \ - static enum XML_Convert_Result PTRCALL E##toUtf8( \ + static enum XML_Convert_Result E##toUtf8( \ const ENCODING *enc, const char **fromP, const char *fromLim, \ char **toP, const char *toLim) { \ const char *from = *fromP; \ @@ -702,7 +716,7 @@ unicode_byte_type(char hi, char lo) { } #define DEFINE_UTF16_TO_UTF16(E) \ - static enum XML_Convert_Result PTRCALL E##toUtf16( \ + static enum XML_Convert_Result E##toUtf16( \ const ENCODING *enc, const char **fromP, const char *fromLim, \ unsigned short **toP, const unsigned short *toLim) { \ enum XML_Convert_Result res = XML_CONVERT_COMPLETED; \ @@ -749,38 +763,49 @@ DEFINE_UTF16_TO_UTF16(big2_) UCS2_GET_NAMING(namePages, (unsigned char)p[1], (unsigned char)p[0]) #define LITTLE2_IS_NMSTRT_CHAR_MINBPC(p) \ UCS2_GET_NAMING(nmstrtPages, (unsigned char)p[1], (unsigned char)p[0]) +/* A 4-byte UTF-16 character is a surrogate pair; byteType only reports BT_LEAD4 + for a high surrogate, so the pair is invalid unless the second unit is a low + surrogate (U+DC00..U+DFFF, i.e. high byte 0xDC..0xDF). */ +#define LITTLE2_IS_INVALID_CHAR(p, n) \ + ((n) == 4 && ((unsigned char)(p)[3] & 0xFC) != 0xDC) #ifdef XML_MIN_SIZE -static int PTRFASTCALL +static int little2_byteType(const ENCODING *enc, const char *p) { return LITTLE2_BYTE_TYPE(enc, p); } -static int PTRFASTCALL +static int little2_byteToAscii(const ENCODING *enc, const char *p) { UNUSED_P(enc); return LITTLE2_BYTE_TO_ASCII(p); } -static int PTRCALL +static int little2_charMatches(const ENCODING *enc, const char *p, int c) { UNUSED_P(enc); return LITTLE2_CHAR_MATCHES(p, c); } -static int PTRFASTCALL +static int little2_isNameMin(const ENCODING *enc, const char *p) { UNUSED_P(enc); return LITTLE2_IS_NAME_CHAR_MINBPC(p); } -static int PTRFASTCALL +static int little2_isNmstrtMin(const ENCODING *enc, const char *p) { UNUSED_P(enc); return LITTLE2_IS_NMSTRT_CHAR_MINBPC(p); } +static int +little2_isInvalid4(const ENCODING *enc, const char *p) { + UNUSED_P(enc); + return LITTLE2_IS_INVALID_CHAR(p, 4); +} + # undef VTABLE # define VTABLE VTABLE1, little2_toUtf8, little2_toUtf16 @@ -797,6 +822,7 @@ little2_isNmstrtMin(const ENCODING *enc, const char *p) { # define IS_NAME_CHAR_MINBPC(enc, p) LITTLE2_IS_NAME_CHAR_MINBPC(p) # define IS_NMSTRT_CHAR(enc, p, n) (0) # define IS_NMSTRT_CHAR_MINBPC(enc, p) LITTLE2_IS_NMSTRT_CHAR_MINBPC(p) +# define IS_INVALID_CHAR(enc, p, n) LITTLE2_IS_INVALID_CHAR(p, n) # define XML_TOK_IMPL_C # include "xmltok_impl.c" @@ -828,7 +854,7 @@ static const struct normal_encoding little2_encoding_ns # include "asciitab.h" # include "latin1tab.h" }, - STANDARD_VTABLE(little2_) NULL_VTABLE}; + STANDARD_VTABLE(little2_) UTF16_NULL_VTABLE(little2_)}; #endif @@ -846,7 +872,7 @@ static const struct normal_encoding little2_encoding #undef BT_COLON #include "latin1tab.h" }, - STANDARD_VTABLE(little2_) NULL_VTABLE}; + STANDARD_VTABLE(little2_) UTF16_NULL_VTABLE(little2_)}; #if BYTEORDER != 4321 @@ -858,7 +884,7 @@ static const struct normal_encoding internal_little2_encoding_ns # include "iasciitab.h" # include "latin1tab.h" }, - STANDARD_VTABLE(little2_) NULL_VTABLE}; + STANDARD_VTABLE(little2_) UTF16_NULL_VTABLE(little2_)}; # endif @@ -870,7 +896,7 @@ static const struct normal_encoding internal_little2_encoding # undef BT_COLON # include "latin1tab.h" }, - STANDARD_VTABLE(little2_) NULL_VTABLE}; + STANDARD_VTABLE(little2_) UTF16_NULL_VTABLE(little2_)}; #endif @@ -882,38 +908,49 @@ static const struct normal_encoding internal_little2_encoding UCS2_GET_NAMING(namePages, (unsigned char)p[0], (unsigned char)p[1]) #define BIG2_IS_NMSTRT_CHAR_MINBPC(p) \ UCS2_GET_NAMING(nmstrtPages, (unsigned char)p[0], (unsigned char)p[1]) +/* A 4-byte UTF-16 character is a surrogate pair; byteType only reports BT_LEAD4 + for a high surrogate, so the pair is invalid unless the second unit is a low + surrogate (U+DC00..U+DFFF, i.e. high byte 0xDC..0xDF). */ +#define BIG2_IS_INVALID_CHAR(p, n) \ + ((n) == 4 && ((unsigned char)(p)[2] & 0xFC) != 0xDC) #ifdef XML_MIN_SIZE -static int PTRFASTCALL +static int big2_byteType(const ENCODING *enc, const char *p) { return BIG2_BYTE_TYPE(enc, p); } -static int PTRFASTCALL +static int big2_byteToAscii(const ENCODING *enc, const char *p) { UNUSED_P(enc); return BIG2_BYTE_TO_ASCII(p); } -static int PTRCALL +static int big2_charMatches(const ENCODING *enc, const char *p, int c) { UNUSED_P(enc); return BIG2_CHAR_MATCHES(p, c); } -static int PTRFASTCALL +static int big2_isNameMin(const ENCODING *enc, const char *p) { UNUSED_P(enc); return BIG2_IS_NAME_CHAR_MINBPC(p); } -static int PTRFASTCALL +static int big2_isNmstrtMin(const ENCODING *enc, const char *p) { UNUSED_P(enc); return BIG2_IS_NMSTRT_CHAR_MINBPC(p); } +static int +big2_isInvalid4(const ENCODING *enc, const char *p) { + UNUSED_P(enc); + return BIG2_IS_INVALID_CHAR(p, 4); +} + # undef VTABLE # define VTABLE VTABLE1, big2_toUtf8, big2_toUtf16 @@ -930,6 +967,7 @@ big2_isNmstrtMin(const ENCODING *enc, const char *p) { # define IS_NAME_CHAR_MINBPC(enc, p) BIG2_IS_NAME_CHAR_MINBPC(p) # define IS_NMSTRT_CHAR(enc, p, n) (0) # define IS_NMSTRT_CHAR_MINBPC(enc, p) BIG2_IS_NMSTRT_CHAR_MINBPC(p) +# define IS_INVALID_CHAR(enc, p, n) BIG2_IS_INVALID_CHAR(p, n) # define XML_TOK_IMPL_C # include "xmltok_impl.c" @@ -961,7 +999,7 @@ static const struct normal_encoding big2_encoding_ns # include "asciitab.h" # include "latin1tab.h" }, - STANDARD_VTABLE(big2_) NULL_VTABLE}; + STANDARD_VTABLE(big2_) UTF16_NULL_VTABLE(big2_)}; #endif @@ -979,7 +1017,7 @@ static const struct normal_encoding big2_encoding #undef BT_COLON #include "latin1tab.h" }, - STANDARD_VTABLE(big2_) NULL_VTABLE}; + STANDARD_VTABLE(big2_) UTF16_NULL_VTABLE(big2_)}; #if BYTEORDER != 1234 @@ -991,7 +1029,7 @@ static const struct normal_encoding internal_big2_encoding_ns # include "iasciitab.h" # include "latin1tab.h" }, - STANDARD_VTABLE(big2_) NULL_VTABLE}; + STANDARD_VTABLE(big2_) UTF16_NULL_VTABLE(big2_)}; # endif @@ -1003,13 +1041,13 @@ static const struct normal_encoding internal_big2_encoding # undef BT_COLON # include "latin1tab.h" }, - STANDARD_VTABLE(big2_) NULL_VTABLE}; + STANDARD_VTABLE(big2_) UTF16_NULL_VTABLE(big2_)}; #endif #undef PREFIX -static int FASTCALL +static int streqci(const char *s1, const char *s2) { for (;;) { char c1 = *s1++; @@ -1030,7 +1068,7 @@ streqci(const char *s1, const char *s2) { return 1; } -static void PTRCALL +static void initUpdatePosition(const ENCODING *enc, const char *ptr, const char *end, POSITION *pos) { UNUSED_P(enc); @@ -1048,7 +1086,7 @@ toAscii(const ENCODING *enc, const char *ptr, const char *end) { return buf[0]; } -static int FASTCALL +static int isSpace(int c) { switch (c) { case 0x20: @@ -1154,6 +1192,34 @@ static const char KW_yes[] = {ASCII_y, ASCII_e, ASCII_s, '\0'}; static const char KW_no[] = {ASCII_n, ASCII_o, '\0'}; +static const char KW_1_dot[] = {ASCII_1, ASCII_PERIOD, '\0'}; + +/* Checks a version pseudo-attribute value against the VersionNum production. + XML 1.0 Fourth Edition only allows the literal "1.0", but the Fifth + Edition relaxed this to "1." followed by one or more digits, since Expat + only implements 1.0 itself but plans to track the Fifth Edition's laxer + grammar here so that "1.1" and similar aren't rejected only to have that + rejection reverted later. Returns true for a value matching "1.[0-9]+". + val/valEnd bound the value itself; valEnd is the upper bound used when + decoding the individual characters between them. */ +static bool +checkXmlDeclVersionNum(const ENCODING *enc, const char *val, + const char *valEnd) { + if (valEnd - val < 2 * enc->minBytesPerChar + || ! XmlNameMatchesAscii(enc, val, val + 2 * enc->minBytesPerChar, + KW_1_dot)) + return false; + val += 2 * enc->minBytesPerChar; + if (val == valEnd) + return false; + for (; val != valEnd; val += enc->minBytesPerChar) { + int c = toAscii(enc, val, valEnd); + if (c < ASCII_0 || c > ASCII_9) + return false; + } + return true; +} + static int doParseXmlDecl(const ENCODING *(*encodingFinder)(const ENCODING *, const char *, const char *), @@ -1188,6 +1254,15 @@ doParseXmlDecl(const ENCODING *(*encodingFinder)(const ENCODING *, const char *, *badPtr = val; return 0; } + /* Expat implements XML 1.0 only, so any version outside the "1.0"/"1.x" + family is rejected. Following the Fifth Edition's VersionNum + production (rather than the Fourth Edition's exact "1.0") avoids + rejecting "1.1" now only to have to revert that once Expat tracks + the newer edition. */ + if (! checkXmlDeclVersionNum(enc, val, ptr - enc->minBytesPerChar)) { + *badPtr = val; + return 0; + } if (! parsePseudoAttribute(enc, ptr, end, &name, &nameEnd, &val, &ptr)) { *badPtr = ptr; return 0; @@ -1242,7 +1317,7 @@ doParseXmlDecl(const ENCODING *(*encodingFinder)(const ENCODING *, const char *, return 1; } -static int FASTCALL +static int checkCharRefNumber(int result) { switch (result >> 8) { case 0xD8: @@ -1266,7 +1341,7 @@ checkCharRefNumber(int result) { return result; } -int FASTCALL +int XmlUtf8Encode(int c, char *buf) { enum { /* minN is minimum legal resulting value for N byte sequence */ @@ -1302,7 +1377,7 @@ XmlUtf8Encode(int c, char *buf) { return 0; /* LCOV_EXCL_LINE: this case too is eliminated before calling */ } -int FASTCALL +int XmlUtf16Encode(int charNum, unsigned short *buf) { if (charNum < 0) return 0; @@ -1334,7 +1409,7 @@ XmlSizeOfUnknownEncoding(void) { return sizeof(struct unknown_encoding); } -static int PTRFASTCALL +static int unknown_isName(const ENCODING *enc, const char *p) { const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); int c = uenc->convert(uenc->userData, p); @@ -1343,7 +1418,7 @@ unknown_isName(const ENCODING *enc, const char *p) { return UCS2_GET_NAMING(namePages, c >> 8, c & 0xFF); } -static int PTRFASTCALL +static int unknown_isNmstrt(const ENCODING *enc, const char *p) { const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); int c = uenc->convert(uenc->userData, p); @@ -1352,14 +1427,14 @@ unknown_isNmstrt(const ENCODING *enc, const char *p) { return UCS2_GET_NAMING(nmstrtPages, c >> 8, c & 0xFF); } -static int PTRFASTCALL +static int unknown_isInvalid(const ENCODING *enc, const char *p) { const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); int c = uenc->convert(uenc->userData, p); return (c & ~0xFFFF) || checkCharRefNumber(c) < 0; } -static enum XML_Convert_Result PTRCALL +static enum XML_Convert_Result unknown_toUtf8(const ENCODING *enc, const char **fromP, const char *fromLim, char **toP, const char *toLim) { const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); @@ -1389,7 +1464,7 @@ unknown_toUtf8(const ENCODING *enc, const char **fromP, const char *fromLim, } } -static enum XML_Convert_Result PTRCALL +static enum XML_Convert_Result unknown_toUtf16(const ENCODING *enc, const char **fromP, const char *fromLim, unsigned short **toP, const unsigned short *toLim) { const struct unknown_encoding *uenc = AS_UNKNOWN_ENCODING(enc); @@ -1513,7 +1588,7 @@ static const char KW_UTF_16LE[] = {ASCII_U, ASCII_T, ASCII_F, ASCII_MINUS, ASCII_1, ASCII_6, ASCII_L, ASCII_E, '\0'}; -static int FASTCALL +static int getEncodingIndex(const char *name) { static const char *const encodingNames[] = { KW_ISO_8859_1, KW_US_ASCII, KW_UTF_8, KW_UTF_16, KW_UTF_16BE, KW_UTF_16LE, diff --git a/lib/xmltok.h b/lib/xmltok.h index bd868b87a407..bf641af6d4f7 100644 --- a/lib/xmltok.h +++ b/lib/xmltok.h @@ -41,10 +41,6 @@ # include <stdint.h> // uint64_t -# ifdef __cplusplus -extern "C" { -# endif - /* The following token may be returned by XmlContentTok */ # define XML_TOK_TRAILING_RSQB \ -5 /* ] or ]] at the end of the scan; might be \ @@ -163,41 +159,40 @@ typedef struct { struct encoding; typedef struct encoding ENCODING; -typedef int(PTRCALL *SCANNER)(const ENCODING *, const char *, const char *, - const char **); +typedef int (*SCANNER)(const ENCODING *, const char *, const char *, + const char **); enum XML_Convert_Result { XML_CONVERT_COMPLETED = 0, XML_CONVERT_INPUT_INCOMPLETE = 1, - XML_CONVERT_OUTPUT_EXHAUSTED - = 2 /* and therefore potentially input remaining as well */ + XML_CONVERT_OUTPUT_EXHAUSTED = 2 /* and therefore potentially input remaining + as well */ }; struct encoding { SCANNER scanners[XML_N_STATES]; SCANNER literalScanners[XML_N_LITERAL_TYPES]; - int(PTRCALL *nameMatchesAscii)(const ENCODING *, const char *, const char *, - const char *); - int(PTRFASTCALL *nameLength)(const ENCODING *, const char *); - const char *(PTRFASTCALL *skipS)(const ENCODING *, const char *); - int(PTRCALL *getAtts)(const ENCODING *enc, const char *ptr, int attsMax, - ATTRIBUTE *atts); - int(PTRFASTCALL *charRefNumber)(const ENCODING *enc, const char *ptr); - int(PTRCALL *predefinedEntityName)(const ENCODING *, const char *, - const char *); - void(PTRCALL *updatePosition)(const ENCODING *, const char *ptr, - const char *end, POSITION *); - int(PTRCALL *isPublicId)(const ENCODING *enc, const char *ptr, - const char *end, const char **badPtr); - enum XML_Convert_Result(PTRCALL *utf8Convert)(const ENCODING *enc, - const char **fromP, - const char *fromLim, char **toP, - const char *toLim); - enum XML_Convert_Result(PTRCALL *utf16Convert)(const ENCODING *enc, - const char **fromP, - const char *fromLim, - unsigned short **toP, - const unsigned short *toLim); + int (*nameMatchesAscii)(const ENCODING *, const char *, const char *, + 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); + 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, + POSITION *); + int (*isPublicId)(const ENCODING *enc, const char *ptr, const char *end, + const char **badPtr); + enum XML_Convert_Result (*utf8Convert)(const ENCODING *enc, + const char **fromP, + const char *fromLim, char **toP, + const char *toLim); + enum XML_Convert_Result (*utf16Convert)(const ENCODING *enc, + const char **fromP, + const char *fromLim, + unsigned short **toP, + const unsigned short *toLim); int minBytesPerChar; char isUtf8; char isUtf16; @@ -297,8 +292,8 @@ int XmlInitEncoding(INIT_ENCODING *p, const ENCODING **encPtr, const char *name); const ENCODING *XmlGetUtf8InternalEncoding(void); const ENCODING *XmlGetUtf16InternalEncoding(void); -int FASTCALL XmlUtf8Encode(int charNumber, char *buf); -int FASTCALL XmlUtf16Encode(int charNumber, unsigned short *buf); +int XmlUtf8Encode(int charNumber, char *buf); +int XmlUtf16Encode(int charNumber, unsigned short *buf); int XmlSizeOfUnknownEncoding(void); typedef int(XMLCALL *CONVERTER)(void *userData, const char *p); @@ -318,8 +313,5 @@ const ENCODING *XmlGetUtf8InternalEncodingNS(void); const ENCODING *XmlGetUtf16InternalEncodingNS(void); ENCODING *XmlInitUnknownEncodingNS(void *mem, const int *table, CONVERTER convert, void *userData); -# ifdef __cplusplus -} -# endif #endif /* not XmlTok_INCLUDED */ diff --git a/lib/xmltok_impl.c b/lib/xmltok_impl.c index 1d8e457d9de5..a71a2fd88c70 100644 --- a/lib/xmltok_impl.c +++ b/lib/xmltok_impl.c @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net> Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2002-2016 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2018 Benjamin Peterson <benjamin@python.org> Copyright (c) 2018 Anton Maklakov <antmak.pub@gmail.com> @@ -18,6 +18,7 @@ Copyright (c) 2020 Boris Kolpackov <boris@codesynthesis.com> Copyright (c) 2022 Martin Ettl <ettl.martin78@googlemail.com> Copyright (c) 2026 Nick Begg <nick@stunttruck.net> + Copyright (c) 2026 Kartik Kenchi <netliomax25@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -44,10 +45,6 @@ #ifdef XML_TOK_IMPL_C -# ifndef IS_INVALID_CHAR // i.e. for UTF-16 and XML_MIN_SIZE not defined -# define IS_INVALID_CHAR(enc, ptr, n) (0) -# endif - # define INVALID_LEAD_CASE(n, ptr, nextTokPtr) \ case BT_LEAD##n: \ if (end - ptr < n) \ @@ -144,7 +141,7 @@ /* ptr points to character following "<!-" */ -static int PTRCALL +static int PREFIX(scanComment)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { if (HAS_CHAR(enc, ptr, end)) { @@ -181,7 +178,7 @@ PREFIX(scanComment)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "<!" */ -static int PTRCALL +static int PREFIX(scanDecl)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { REQUIRE_CHAR(enc, ptr, end); @@ -230,7 +227,7 @@ PREFIX(scanDecl)(const ENCODING *enc, const char *ptr, const char *end, return XML_TOK_PARTIAL; } -static int PTRCALL +static int PREFIX(checkPiTarget)(const ENCODING *enc, const char *ptr, const char *end, int *tokPtr) { int upper = 0; @@ -275,7 +272,7 @@ PREFIX(checkPiTarget)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "<?" */ -static int PTRCALL +static int PREFIX(scanPi)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { int tok; @@ -335,7 +332,7 @@ PREFIX(scanPi)(const ENCODING *enc, const char *ptr, const char *end, return XML_TOK_PARTIAL; } -static int PTRCALL +static int PREFIX(scanCdataSection)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { static const char CDATA_LSQB[] @@ -354,7 +351,7 @@ PREFIX(scanCdataSection)(const ENCODING *enc, const char *ptr, const char *end, return XML_TOK_CDATA_SECT_OPEN; } -static int PTRCALL +static int PREFIX(cdataSectionTok)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { if (ptr >= end) @@ -430,7 +427,7 @@ PREFIX(cdataSectionTok)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "</" */ -static int PTRCALL +static int PREFIX(scanEndTag)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { REQUIRE_CHAR(enc, ptr, end); @@ -481,7 +478,7 @@ PREFIX(scanEndTag)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "&#X" */ -static int PTRCALL +static int PREFIX(scanHexCharRef)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { if (HAS_CHAR(enc, ptr, end)) { @@ -512,7 +509,7 @@ PREFIX(scanHexCharRef)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "&#" */ -static int PTRCALL +static int PREFIX(scanCharRef)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { if (HAS_CHAR(enc, ptr, end)) { @@ -543,7 +540,7 @@ PREFIX(scanCharRef)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "&" */ -static int PTRCALL +static int PREFIX(scanRef)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { REQUIRE_CHAR(enc, ptr, end); @@ -571,7 +568,7 @@ PREFIX(scanRef)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following first character of attribute name */ -static int PTRCALL +static int PREFIX(scanAtts)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { # ifdef XML_NS @@ -724,7 +721,7 @@ PREFIX(scanAtts)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "<" */ -static int PTRCALL +static int PREFIX(scanLt)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { # ifdef XML_NS @@ -822,7 +819,7 @@ PREFIX(scanLt)(const ENCODING *enc, const char *ptr, const char *end, return XML_TOK_PARTIAL; } -static int PTRCALL +static int PREFIX(contentTok)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { if (ptr >= end) @@ -922,7 +919,7 @@ PREFIX(contentTok)(const ENCODING *enc, const char *ptr, const char *end, /* ptr points to character following "%" */ -static int PTRCALL +static int PREFIX(scanPercent)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { REQUIRE_CHAR(enc, ptr, end); @@ -952,7 +949,7 @@ PREFIX(scanPercent)(const ENCODING *enc, const char *ptr, const char *end, return XML_TOK_PARTIAL; } -static int PTRCALL +static int PREFIX(scanPoundName)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { REQUIRE_CHAR(enc, ptr, end); @@ -982,7 +979,7 @@ PREFIX(scanPoundName)(const ENCODING *enc, const char *ptr, const char *end, return -XML_TOK_POUND_NAME; } -static int PTRCALL +static int PREFIX(scanLit)(int open, const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { while (HAS_CHAR(enc, ptr, end)) { @@ -1016,7 +1013,7 @@ PREFIX(scanLit)(int open, const ENCODING *enc, const char *ptr, const char *end, return XML_TOK_PARTIAL; } -static int PTRCALL +static int PREFIX(prologTok)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { int tok; @@ -1260,7 +1257,7 @@ PREFIX(prologTok)(const ENCODING *enc, const char *ptr, const char *end, return -tok; } -static int PTRCALL +static int PREFIX(attributeValueTok)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { const char *start; @@ -1329,7 +1326,7 @@ PREFIX(attributeValueTok)(const ENCODING *enc, const char *ptr, const char *end, return XML_TOK_DATA_CHARS; } -static int PTRCALL +static int PREFIX(entityValueTok)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { const char *start; @@ -1396,7 +1393,7 @@ PREFIX(entityValueTok)(const ENCODING *enc, const char *ptr, const char *end, # ifdef XML_DTD -static int PTRCALL +static int PREFIX(ignoreSectionTok)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { int level = 0; @@ -1448,7 +1445,7 @@ PREFIX(ignoreSectionTok)(const ENCODING *enc, const char *ptr, const char *end, # endif /* XML_DTD */ -static int PTRCALL +static int PREFIX(isPublicId)(const ENCODING *enc, const char *ptr, const char *end, const char **badPtr) { ptr += MINBPC(enc); @@ -1508,7 +1505,7 @@ PREFIX(isPublicId)(const ENCODING *enc, const char *ptr, const char *end, first attsMax attributes are stored in atts. */ -static int PTRCALL +static int PREFIX(getAtts)(const ENCODING *enc, const char *ptr, int attsMax, ATTRIBUTE *atts) { enum { other, inName, inValue } state = inName; @@ -1601,7 +1598,7 @@ PREFIX(getAtts)(const ENCODING *enc, const char *ptr, int attsMax, /* not reached */ } -static int PTRFASTCALL +static int PREFIX(charRefNumber)(const ENCODING *enc, const char *ptr) { int result = 0; /* skip &# */ @@ -1659,7 +1656,7 @@ PREFIX(charRefNumber)(const ENCODING *enc, const char *ptr) { return checkCharRefNumber(result); } -static int PTRCALL +static int PREFIX(predefinedEntityName)(const ENCODING *enc, const char *ptr, const char *end) { UNUSED_P(enc); @@ -1713,7 +1710,7 @@ PREFIX(predefinedEntityName)(const ENCODING *enc, const char *ptr, return 0; } -static int PTRCALL +static int PREFIX(nameMatchesAscii)(const ENCODING *enc, const char *ptr1, const char *end1, const char *ptr2) { UNUSED_P(enc); @@ -1732,7 +1729,7 @@ PREFIX(nameMatchesAscii)(const ENCODING *enc, const char *ptr1, return ptr1 == end1; } -static int PTRFASTCALL +static int PREFIX(nameLength)(const ENCODING *enc, const char *ptr) { const char *start = ptr; for (;;) { @@ -1762,7 +1759,7 @@ PREFIX(nameLength)(const ENCODING *enc, const char *ptr) { } } -static const char *PTRFASTCALL +static const char * PREFIX(skipS)(const ENCODING *enc, const char *ptr) { for (;;) { switch (BYTE_TYPE(enc, ptr)) { @@ -1777,7 +1774,7 @@ PREFIX(skipS)(const ENCODING *enc, const char *ptr) { } } -static void PTRCALL +static void PREFIX(updatePosition)(const ENCODING *enc, const char *ptr, const char *end, POSITION *pos) { while (HAS_CHAR(enc, ptr, end)) { diff --git a/lib/xmltok_ns.c b/lib/xmltok_ns.c index 40a02a0308b5..d9f69abb92ef 100644 --- a/lib/xmltok_ns.c +++ b/lib/xmltok_ns.c @@ -11,7 +11,7 @@ Copyright (c) 2002 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2017-2021 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2017-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2025 Alfonso Gregory <gfunni234@gmail.com> Licensed under the MIT license: @@ -64,14 +64,14 @@ static const ENCODING *const NS(encodings)[] = { &ns(utf8_encoding).enc /* NO_ENC */ }; -static int PTRCALL +static int NS(initScanProlog)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { return initScan(NS(encodings), (const INIT_ENCODING *)enc, XML_PROLOG_STATE, ptr, end, nextTokPtr); } -static int PTRCALL +static int NS(initScanContent)(const ENCODING *enc, const char *ptr, const char *end, const char **nextTokPtr) { return initScan(NS(encodings), (const INIT_ENCODING *)enc, XML_CONTENT_STATE, diff --git a/tests/Makefile.am b/tests/Makefile.am index eefcfe023501..21d8df2c6676 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -49,6 +49,7 @@ runtests_SOURCES = \ common.c \ dummy.c \ handlers.c \ + hash_tests.c \ memcheck.c \ minicheck.c \ misc_tests.c \ @@ -69,13 +70,14 @@ EXTRA_DIST = \ common.h \ dummy.h \ handlers.h \ + hash_tests.h \ + memcheck.h \ + minicheck.h \ misc_tests.h \ ns_tests.h \ nsalloc_tests.h \ - structdata.h \ - minicheck.h \ - memcheck.h \ README.md \ + structdata.h \ udiffer.py \ xmltest.log.expected \ xmltest.sh diff --git a/tests/Makefile.in b/tests/Makefile.in index 97b22713663f..f81e113c2216 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. +# Makefile.in generated by automake 1.19 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2025 Free Software Foundation, Inc. +# Copyright (C) 1994-2026 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -149,9 +149,9 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am_runtests_OBJECTS = acc_tests.$(OBJEXT) alloc_tests.$(OBJEXT) \ basic_tests.$(OBJEXT) chardata.$(OBJEXT) common.$(OBJEXT) \ - dummy.$(OBJEXT) handlers.$(OBJEXT) memcheck.$(OBJEXT) \ - minicheck.$(OBJEXT) misc_tests.$(OBJEXT) ns_tests.$(OBJEXT) \ - nsalloc_tests.$(OBJEXT) runtests.$(OBJEXT) \ + dummy.$(OBJEXT) handlers.$(OBJEXT) hash_tests.$(OBJEXT) \ + memcheck.$(OBJEXT) minicheck.$(OBJEXT) misc_tests.$(OBJEXT) \ + ns_tests.$(OBJEXT) nsalloc_tests.$(OBJEXT) runtests.$(OBJEXT) \ structdata.$(OBJEXT) runtests_OBJECTS = $(am_runtests_OBJECTS) runtests_DEPENDENCIES = ../lib/libtestpat.la @@ -181,10 +181,10 @@ 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)/memcheck.Po ./$(DEPDIR)/minicheck.Po \ - ./$(DEPDIR)/misc_tests.Po ./$(DEPDIR)/ns_tests.Po \ - ./$(DEPDIR)/nsalloc_tests.Po ./$(DEPDIR)/runtests.Po \ - ./$(DEPDIR)/structdata.Po + ./$(DEPDIR)/hash_tests.Po ./$(DEPDIR)/memcheck.Po \ + ./$(DEPDIR)/minicheck.Po ./$(DEPDIR)/misc_tests.Po \ + ./$(DEPDIR)/ns_tests.Po ./$(DEPDIR)/nsalloc_tests.Po \ + ./$(DEPDIR)/runtests.Po ./$(DEPDIR)/structdata.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -594,6 +594,7 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ +am__tar_ignore_stderr = @am__tar_ignore_stderr@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ @@ -649,6 +650,7 @@ runtests_SOURCES = \ common.c \ dummy.c \ handlers.c \ + hash_tests.c \ memcheck.c \ minicheck.c \ misc_tests.c \ @@ -667,13 +669,14 @@ EXTRA_DIST = \ common.h \ dummy.h \ handlers.h \ + hash_tests.h \ + memcheck.h \ + minicheck.h \ misc_tests.h \ ns_tests.h \ nsalloc_tests.h \ - structdata.h \ - minicheck.h \ - memcheck.h \ README.md \ + structdata.h \ udiffer.py \ xmltest.log.expected \ xmltest.sh @@ -691,9 +694,9 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi exit 1;; \ esac; \ done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/Makefile'; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tests/Makefile'; \ $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu tests/Makefile + $(AUTOMAKE) --foreign tests/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -733,6 +736,7 @@ distclean-compile: @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)/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 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/minicheck.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc_tests.Po@am__quote@ # am--include-marker @@ -748,22 +752,25 @@ $(am__depfiles_remade): am--depfiles: $(am__depfiles_remade) .c.o: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< @@ -1067,13 +1074,14 @@ distdir: $(BUILT_SOURCES) distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + am__distdir="$(distdir)"; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sed "/\//!d;s|^|$$am__distdir/|;s,/[^/]*$$,," | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ @@ -1172,6 +1180,7 @@ distclean: distclean-recursive -rm -f ./$(DEPDIR)/common.Po -rm -f ./$(DEPDIR)/dummy.Po -rm -f ./$(DEPDIR)/handlers.Po + -rm -f ./$(DEPDIR)/hash_tests.Po -rm -f ./$(DEPDIR)/memcheck.Po -rm -f ./$(DEPDIR)/minicheck.Po -rm -f ./$(DEPDIR)/misc_tests.Po @@ -1231,6 +1240,7 @@ maintainer-clean: maintainer-clean-recursive -rm -f ./$(DEPDIR)/common.Po -rm -f ./$(DEPDIR)/dummy.Po -rm -f ./$(DEPDIR)/handlers.Po + -rm -f ./$(DEPDIR)/hash_tests.Po -rm -f ./$(DEPDIR)/memcheck.Po -rm -f ./$(DEPDIR)/minicheck.Po -rm -f ./$(DEPDIR)/misc_tests.Po diff --git a/tests/acc_tests.h b/tests/acc_tests.h index 6fc0dcac9d98..49a3cd944232 100644 --- a/tests/acc_tests.h +++ b/tests/acc_tests.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -42,17 +42,9 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_ACC_TESTS_H -# define XML_ACC_TESTS_H +#define XML_ACC_TESTS_H extern void make_accounting_test_case(Suite *s); #endif /* XML_ACC_TESTS_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/alloc_tests.h b/tests/alloc_tests.h index 6dcc7b4cf513..97f3f91ee795 100644 --- a/tests/alloc_tests.h +++ b/tests/alloc_tests.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -42,17 +42,9 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_ALLOC_TESTS_H -# define XML_ALLOC_TESTS_H +#define XML_ALLOC_TESTS_H extern void make_alloc_test_case(Suite *s); #endif /* XML_ALLOC_TESTS_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/basic_tests.c b/tests/basic_tests.c index 308adf6cbce0..8f4f0902d5c7 100644 --- a/tests/basic_tests.c +++ b/tests/basic_tests.c @@ -23,6 +23,9 @@ Copyright (c) 2026 Francesco Bertolaccini Copyright (c) 2026 Matthew Fernandez <matthew.fernandez@gmail.com> Copyright (c) 2026 Kartik Kenchi <netliomax25@gmail.com> + Copyright (c) 2026 Zeyou Liu <zeyouliu@tencent.com> + Copyright (c) 2026 Afonso Januário <afonso-januario@hotmail.com> + Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -213,7 +216,7 @@ START_TEST(test_hash_collision) { "<d8>This triggers the table growth and collides with b2</d8>\n" "</doc>\n"; - XML_SetHashSalt(g_parser, COLLIDING_HASH_SALT); + xmlSetHashSalt(g_parser, COLLIDING_HASH_SALT); if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE) == XML_STATUS_ERROR) xml_failure(g_parser); @@ -997,6 +1000,50 @@ START_TEST(test_xmldecl_empty_version) { } END_TEST +/* Regression test for GH #967: Expat only implements XML 1.0 Fourth + Edition, so a declared version outside the "1.x" family must be + rejected rather than silently accepted. A version matching the Fifth + Edition's VersionNum production ("1." followed by one or more digits) + is accepted even though Expat itself only implements 1.0 Fourth + Edition, per GH #967's review thread: rejecting "1.1" now would just + have to be reverted once Expat tracks the Fifth Edition, so it's let + through rather than blocked twice. */ +START_TEST(test_xmldecl_wrong_version_number) { + const char *const badVersions[] = {"2.3", "0.9", "10", "1.", "1", " 1.0 "}; + + for (size_t i = 0; i < sizeof(badVersions) / sizeof(badVersions[0]); i++) { + char doc[64]; + snprintf(doc, sizeof(doc), "<?xml version='%s'?>\n<doc/>", badVersions[i]); + set_subtest("version='%s'", badVersions[i]); + + XML_Parser parser = XML_ParserCreate(NULL); + assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc), XML_TRUE) + == XML_STATUS_ERROR); + assert_true(XML_GetErrorCode(parser) == XML_ERROR_XML_DECL); + XML_ParserFree(parser); + } +} +END_TEST + +/* GH #967's review pointed out that XML 1.0 Fifth Edition relaxed + VersionNum to "1." followed by one or more digits, so versions like + "1.1" and "1.123" should parse rather than being rejected. */ +START_TEST(test_xmldecl_accepts_1_x_version) { + const char *const goodVersions[] = {"1.1", "1.123"}; + + for (size_t i = 0; i < sizeof(goodVersions) / sizeof(goodVersions[0]); i++) { + char doc[64]; + snprintf(doc, sizeof(doc), "<?xml version='%s'?>\n<doc/>", goodVersions[i]); + set_subtest("version='%s'", goodVersions[i]); + + XML_Parser parser = XML_ParserCreate(NULL); + assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc), XML_TRUE) + == XML_STATUS_OK); + XML_ParserFree(parser); + } +} +END_TEST + /* Regression test for SF bug #584832. */ START_TEST(test_unknown_encoding_internal_entity) { const char *text = "<?xml version='1.0' encoding='unsupported-encoding'?>\n" @@ -1845,6 +1892,301 @@ START_TEST(test_utf16_bad_surrogate_pair) { } END_TEST +// Helper that creates a UTF-16LE copy of UTF-16BE literal input and vice versa +static char * +utf16_dup_flipped(const char *text, size_t lenBytes) { + assert_true(lenBytes < SIZE_MAX); + assert_true(lenBytes % 2 == 0); + char *const buffer = malloc(lenBytes + 1); + assert_true(buffer != NULL); + + for (size_t i = 0; i < lenBytes; i++) { + // This maps 0 -> 1, 1 -> 0, 2 -> 3, 3 -> 2, 4 -> 5, .. + size_t j = i + ((i % 2 == 0) ? +1 : -1); + assert_true(j < lenBytes); + buffer[j] = text[i]; + } + + buffer[lenBytes] = '\0'; + + return buffer; +} + +/* Tests that invalid combinations of surrogates are detected when decoding + UTF-16, both little-endian and big-endian. + Previously, a high surrogate not followed by a low surrogate slipped + through. Without validation the high would consume the next + code unit as a fake low, hiding e.g. a following '<' from the + tokenizer. */ +START_TEST(test_utf16_surrogate_pairs) { + struct TestCase { + const char *idea; + const char *content; + bool expectedSuccess; + }; + + struct TestCase testCases[] = { + // Group {smallest high - 1}{*} + {"{smallest high - 1}{smallest high - 1}", + "\0<\0a\0>" + "\xD7\xFF" + "\xD7\xFF" + "\0<\0/\0a\0>", + true}, + {"{smallest high - 1}{smallest high}", + "\0<\0a\0>" + "\xD7\xFF" + "\xD8\x00" + "\0<\0/\0a\0>", + false}, + {"{smallest high - 1}{largest high}", + "\0<\0a\0>" + "\xD7\xFF" + "\xDB\xFF" + "\0<\0/\0a\0>", + false}, + {"{smallest high - 1}{smallest low}", + "\0<\0a\0>" + "\xD7\xFF" + "\xDC\x00" + "\0<\0/\0a\0>", + false}, + {"{smallest high - 1}{largest low}", + "\0<\0a\0>" + "\xD7\xFF" + "\xDF\xFF" + "\0<\0/\0a\0>", + false}, + {"{smallest high - 1}{largest low + 1}", + "\0<\0a\0>" + "\xD7\xFF" + "\xE0\x00" + "\0<\0/\0a\0>", + true}, + // Group {smallest high}{*} + {"{smallest high}{smallest high - 1}", + "\0<\0a\0>" + "\xD8\x00" + "\xD7\xFF" + "\0<\0/\0a\0>", + false}, + {"{smallest high}{smallest high}", + "\0<\0a\0>" + "\xD8\x00" + "\xD8\x00" + "\0<\0/\0a\0>", + false}, + {"{smallest high}{largest high}", + "\0<\0a\0>" + "\xD8\x00" + "\xDB\xFF" + "\0<\0/\0a\0>", + false}, + {"{smallest high}{smallest low}", + "\0<\0a\0>" + "\xD8\x00" + "\xDC\x00" + "\0<\0/\0a\0>", + true}, + {"{smallest high}{largest low}", + "\0<\0a\0>" + "\xD8\x00" + "\xDF\xFF" + "\0<\0/\0a\0>", + true}, + {"{smallest high}{largest low + 1}", + "\0<\0a\0>" + "\xD8\x00" + "\xE0\x00" + "\0<\0/\0a\0>", + false}, + // Group {largest high}{*} + {"{largest high}{smallest high - 1}", + "\0<\0a\0>" + "\xDB\xFF" + "\xD7\xFF" + "\0<\0/\0a\0>", + false}, + {"{largest high}{smallest high}", + "\0<\0a\0>" + "\xDB\xFF" + "\xD8\x00" + "\0<\0/\0a\0>", + false}, + {"{largest high}{largest high}", + "\0<\0a\0>" + "\xDB\xFF" + "\xDB\xFF" + "\0<\0/\0a\0>", + false}, + {"{largest high}{smallest low}", + "\0<\0a\0>" + "\xDB\xFF" + "\xDC\x00" + "\0<\0/\0a\0>", + true}, + {"{largest high}{largest low}", + "\0<\0a\0>" + "\xDB\xFF" + "\xDF\xFF" + "\0<\0/\0a\0>", + true}, + {"{largest high}{largest low + 1}", + "\0<\0a\0>" + "\xDB\xFF" + "\xE0\x00" + "\0<\0/\0a\0>", + false}, + // Group {smallest low}{*} + {"{smallest low}{smallest high - 1}", + "\0<\0a\0>" + "\xDC\x00" + "\xD7\xFF" + "\0<\0/\0a\0>", + false}, + {"{smallest low}{smallest high}", + "\0<\0a\0>" + "\xDC\x00" + "\xD8\x00" + "\0<\0/\0a\0>", + false}, + {"{smallest low}{largest high}", + "\0<\0a\0>" + "\xDC\x00" + "\xDB\xFF" + "\0<\0/\0a\0>", + false}, + {"{smallest low}{smallest low}", + "\0<\0a\0>" + "\xDC\x00" + "\xDC\x00" + "\0<\0/\0a\0>", + false}, + {"{smallest low}{largest low}", + "\0<\0a\0>" + "\xDC\x00" + "\xDF\xFF" + "\0<\0/\0a\0>", + false}, + {"{smallest low}{largest low + 1}", + "\0<\0a\0>" + "\xDC\x00" + "\xE0\x00" + "\0<\0/\0a\0>", + false}, + // Group {largest low}{*} + {"{largest low}{smallest high - 1}", + "\0<\0a\0>" + "\xDF\xFF" + "\xD7\xFF" + "\0<\0/\0a\0>", + false}, + {"{largest low}{smallest high}", + "\0<\0a\0>" + "\xDF\xFF" + "\xD8\x00" + "\0<\0/\0a\0>", + false}, + {"{largest low}{largest high}", + "\0<\0a\0>" + "\xDF\xFF" + "\xDB\xFF" + "\0<\0/\0a\0>", + false}, + {"{largest low}{smallest low}", + "\0<\0a\0>" + "\xDF\xFF" + "\xDC\x00" + "\0<\0/\0a\0>", + false}, + {"{largest low}{largest low}", + "\0<\0a\0>" + "\xDF\xFF" + "\xDF\xFF" + "\0<\0/\0a\0>", + false}, + {"{largest low}{largest low + 1}", + "\0<\0a\0>" + "\xDF\xFF" + "\xE0\x00" + "\0<\0/\0a\0>", + false}, + // Group {largest low + 1}{*} + {"{largest low + 1}{smallest high - 1}", + "\0<\0a\0>" + "\xE0\x00" + "\xD7\xFF" + "\0<\0/\0a\0>", + true}, + {"{largest low + 1}{smallest high}", + "\0<\0a\0>" + "\xE0\x00" + "\xD8\x00" + "\0<\0/\0a\0>", + false}, + {"{largest low + 1}{largest high}", + "\0<\0a\0>" + "\xE0\x00" + "\xDB\xFF" + "\0<\0/\0a\0>", + false}, + {"{largest low + 1}{smallest low}", + "\0<\0a\0>" + "\xE0\x00" + "\xDC\x00" + "\0<\0/\0a\0>", + false}, + {"{largest low + 1}{largest low}", + "\0<\0a\0>" + "\xE0\x00" + "\xDF\xFF" + "\0<\0/\0a\0>", + false}, + {"{largest low + 1}{largest low + 1}", + "\0<\0a\0>" + "\xE0\x00" + "\xE0\x00" + "\0<\0/\0a\0>", + true}, + }; + + for (size_t i = 0; i < sizeof(testCases) / sizeof(testCases[0]); i++) { + set_subtest("%s", testCases[i].idea); + + const int lenBytes = /*<a>*/ 6 + /*first*/ 2 + /*second*/ 2 + /*</a>*/ 8; + const bool expectedSuccess = testCases[i].expectedSuccess; + const enum XML_Status expectedStatus + = (expectedSuccess ? XML_STATUS_OK : XML_STATUS_ERROR); + + const char *const bigEndian = testCases[i].content; + char *const littleEndian = utf16_dup_flipped(bigEndian, lenBytes); + assert_true(littleEndian != NULL); + const char *endianCases[] = {bigEndian, littleEndian}; + + for (size_t j = 0; j < sizeof(endianCases) / sizeof(endianCases[0]); j++) { + const char *text = endianCases[j]; + + assert_true(text[lenBytes] == '\0'); // self-test + assert_true((text[0] == '\0') + != (text[lenBytes - 1] == '\0')); // self-test + + XML_Parser parser = XML_ParserCreate(NULL); + assert_true(parser != NULL); + + assert_true(_XML_Parse_SINGLE_BYTES(parser, text, lenBytes, XML_TRUE) + == expectedStatus); + if (! expectedSuccess) { + assert_true(XML_GetErrorCode(parser) == XML_ERROR_INVALID_TOKEN); + } + + XML_ParserFree(parser); + } + + free(littleEndian); + } +} +END_TEST + START_TEST(test_bad_cdata) { struct CaseData { const char *text; @@ -2357,7 +2699,7 @@ START_TEST(test_set_foreign_dtd) { ExtTest test_data = {"<!ELEMENT doc (#PCDATA)*>", NULL, NULL}; /* Check hash salt is passed through too */ - XML_SetHashSalt(g_parser, 0x12345678); + xmlSetHashSalt(g_parser, 0x12345678); XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS); XML_SetUserData(g_parser, &test_data); XML_SetExternalEntityRefHandler(g_parser, external_entity_loader); @@ -2376,7 +2718,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 (XML_SetHashSalt(g_parser, 0x23456789)) + if (xmlSetHashSalt(g_parser, 0x23456789)) fail("Failed to reject late hash salt change"); /* Now finish the parse */ @@ -2427,7 +2769,7 @@ START_TEST(test_foreign_dtd_with_doctype) { ExtTest test_data = {"<!ELEMENT doc (#PCDATA)*>", NULL, NULL}; /* Check hash salt is passed through too */ - XML_SetHashSalt(g_parser, 0x12345678); + xmlSetHashSalt(g_parser, 0x12345678); XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS); XML_SetUserData(g_parser, &test_data); XML_SetExternalEntityRefHandler(g_parser, external_entity_loader); @@ -2446,7 +2788,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 (XML_SetHashSalt(g_parser, 0x23456789)) + if (xmlSetHashSalt(g_parser, 0x23456789)) fail("Failed to reject late hash salt change"); /* Now finish the parse */ @@ -2809,6 +3151,79 @@ START_TEST(test_duplicate_id_attribute_multiple_attlistdecl) { } END_TEST +static void XMLCALL +check_second_attr_normalization(void *userData, const XML_Char *name, + const XML_Char **atts) { + int *const seen_second = userData; + UNUSED_P(name); + + for (size_t i = 0; atts[i] != NULL; i += 2) { + const XML_Char *const key = atts[i]; + const XML_Char *const value = atts[i + 1]; + if (xcstrcmp(key, XCS("second")) != 0) + continue; + *seen_second = 1; + /* Attribute "second" is not of type CDATA, so leading, trailing and + * repeated whitespace is to be normalized away. */ + if (xcstrcmp(value, XCS("a b")) != 0) + fail("Attribute of non-CDATA type was not whitespace-normalized"); + } +} + +static int XMLCALL +external_entity_attr_checker(XML_Parser parser, const XML_Char *context, + const XML_Char *base, const XML_Char *systemId, + const XML_Char *publicId) { + const char *const text = "<tag second=' a b '/>"; + UNUSED_P(base); + UNUSED_P(systemId); + UNUSED_P(publicId); + + XML_Parser ext_parser = XML_ExternalEntityParserCreate(parser, context, NULL); + if (ext_parser == NULL) + fail("Could not create external entity parser"); + + if (_XML_Parse_SINGLE_BYTES(ext_parser, text, (int)strlen(text), XML_TRUE) + != XML_STATUS_OK) + xml_failure(ext_parser); + + XML_ParserFree(ext_parser); + return XML_STATUS_OK; +} + +START_TEST(test_default_attr_index_after_dtd_copy) { + /* Function storeAtts resolves member .attIndex of structure + * NAME_AND_DEFAULT_ATTRIBUTE to tell whether an attribute value needs + * whitespace normalization, so function dtdCopy needs to carry that index + * over to the copy. Attribute "first" is declared before attribute + * "second" so that a mixed-up index resolves to the wrong declaration. + */ + const char *text = "<!DOCTYPE doc [\n" + " <!ENTITY e SYSTEM 'entity.ent'>\n" + " <!ELEMENT doc ANY>\n" + " <!ELEMENT tag EMPTY>\n" + " <!ATTLIST tag first CDATA #IMPLIED>\n" + " <!ATTLIST tag second NMTOKENS #IMPLIED>\n" + "]>\n" + "<doc>&e;</doc>\n"; + int seen_second = 0; + + XML_Parser parser = XML_ParserCreate(NULL); + assert_true(parser != NULL); + XML_SetUserData(parser, &seen_second); + XML_SetExternalEntityRefHandler(parser, external_entity_attr_checker); + XML_SetStartElementHandler(parser, check_second_attr_normalization); + + if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE) + != XML_STATUS_OK) + xml_failure(parser); + if (! seen_second) + fail("Attribute \"second\" has not been reported"); + + XML_ParserFree(parser); +} +END_TEST + /* Test reset works correctly in the middle of processing an internal * entity. Exercises some obscure code in XML_ParserReset(). */ @@ -3440,6 +3855,8 @@ START_TEST(test_buffer_can_grow_to_max) { maxbuf = maxbuf / 2; fprintf(stderr, "Reducing maxbuf to %d...\n", maxbuf); } +#else + UNUSED_P(maxbuf); #endif for (int i = 0; i < num_prefixes; ++i) { @@ -3463,6 +3880,8 @@ START_TEST(test_buffer_can_grow_to_max) { // The limit should be consistent; no prefix should allow us to // reach above the max buffer size. assert_true(XML_GetBuffer(parser, maxbuf + 1) == NULL); +#else + UNUSED_P(maxbuf); #endif XML_ParserFree(parser); @@ -6678,6 +7097,8 @@ make_basic_test_case(Suite *s) { tcase_add_test(tc_basic, test_xmldecl_missing_attr); tcase_add_test(tc_basic, test_xmldecl_missing_value); tcase_add_test(tc_basic, test_xmldecl_empty_version); + tcase_add_test(tc_basic, test_xmldecl_wrong_version_number); + tcase_add_test(tc_basic, test_xmldecl_accepts_1_x_version); tcase_add_test__if_xml_ge(tc_basic, test_unknown_encoding_internal_entity); tcase_add_test(tc_basic, test_unrecognised_encoding_internal_entity); tcase_add_test__ifdef_xml_dtd(tc_basic, test_ext_entity_set_encoding); @@ -6711,6 +7132,7 @@ make_basic_test_case(Suite *s) { tcase_add_test(tc_basic, test_long_cdata_utf16); tcase_add_test(tc_basic, test_multichar_cdata_utf16); tcase_add_test(tc_basic, test_utf16_bad_surrogate_pair); + tcase_add_test(tc_basic, test_utf16_surrogate_pairs); tcase_add_test(tc_basic, test_bad_cdata); tcase_add_test(tc_basic, test_bad_cdata_utf16); tcase_add_test(tc_basic, test_stop_parser_between_cdata_calls); @@ -6737,6 +7159,7 @@ make_basic_test_case(Suite *s) { tcase_add_test(tc_basic, test_duplicate_cdata_attribute_multiple_attlistdecl_3); tcase_add_test(tc_basic, test_duplicate_id_attribute_multiple_attlistdecl); + tcase_add_test__if_xml_ge(tc_basic, test_default_attr_index_after_dtd_copy); tcase_add_test__if_xml_ge(tc_basic, test_reset_in_entity); tcase_add_test(tc_basic, test_resume_invalid_parse); tcase_add_test(tc_basic, test_resume_resuspended); diff --git a/tests/basic_tests.h b/tests/basic_tests.h index e8a8488773ed..c4b9c6039849 100644 --- a/tests/basic_tests.h +++ b/tests/basic_tests.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -42,17 +42,9 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_BASIC_TESTS_H -# define XML_BASIC_TESTS_H +#define XML_BASIC_TESTS_H extern void make_basic_test_case(Suite *s); #endif /* XML_BASIC_TESTS_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/benchmark/Makefile.in b/tests/benchmark/Makefile.in index 89a29cef053b..4156629984be 100644 --- a/tests/benchmark/Makefile.in +++ b/tests/benchmark/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. +# Makefile.in generated by automake 1.19 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2025 Free Software Foundation, Inc. +# Copyright (C) 1994-2026 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -327,6 +327,7 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ +am__tar_ignore_stderr = @am__tar_ignore_stderr@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ @@ -388,9 +389,9 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi exit 1;; \ esac; \ done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/benchmark/Makefile'; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign tests/benchmark/Makefile'; \ $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu tests/benchmark/Makefile + $(AUTOMAKE) --foreign tests/benchmark/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -432,22 +433,25 @@ $(am__depfiles_remade): am--depfiles: $(am__depfiles_remade) .c.o: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< @@ -516,13 +520,14 @@ distdir: $(BUILT_SOURCES) distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + am__distdir="$(distdir)"; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sed "/\//!d;s|^|$$am__distdir/|;s,/[^/]*$$,," | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ diff --git a/tests/chardata.h b/tests/chardata.h index 51b18afe3ffe..ba6d8054e0cb 100644 --- a/tests/chardata.h +++ b/tests/chardata.h @@ -9,7 +9,7 @@ Copyright (c) 2002-2004 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> - Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2017-2026 Sebastian Pipping <sebastian@pipping.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -34,10 +34,6 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_CHARDATA_H # define XML_CHARDATA_H 1 @@ -57,7 +53,3 @@ void CharData_AppendXMLChars(CharData *storage, const XML_Char *s, int len); int CharData_CheckXMLChars(CharData *storage, const XML_Char *s); #endif /* XML_CHARDATA_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/common.h b/tests/common.h index cf1635de0592..f9bde995e7d2 100644 --- a/tests/common.h +++ b/tests/common.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -43,42 +43,38 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_COMMON_H -# define XML_COMMON_H +#define XML_COMMON_H -# include "expat_config.h" -# include "minicheck.h" -# include "chardata.h" +#include "expat_config.h" +#include "minicheck.h" +#include "chardata.h" -# ifdef XML_LARGE_SIZE -# define XML_FMT_INT_MOD "ll" -# else -# define XML_FMT_INT_MOD "l" -# endif +#ifdef XML_LARGE_SIZE +# define XML_FMT_INT_MOD "ll" +#else +# define XML_FMT_INT_MOD "l" +#endif -# ifdef XML_UNICODE_WCHAR_T -# define XML_FMT_STR "ls" -# include <wchar.h> -# define xcstrlen(s) wcslen(s) -# define xcstrcmp(s, t) wcscmp((s), (t)) -# define xcstrncmp(s, t, n) wcsncmp((s), (t), (n)) -# define XCS(s) _XCS(s) -# define _XCS(s) L##s +#ifdef XML_UNICODE_WCHAR_T +# define XML_FMT_STR "ls" +# include <wchar.h> +# define xcstrlen(s) wcslen(s) +# define xcstrcmp(s, t) wcscmp((s), (t)) +# define xcstrncmp(s, t, n) wcsncmp((s), (t), (n)) +# define XCS(s) _XCS(s) +# define _XCS(s) L##s +#else +# ifdef XML_UNICODE +# error "No support for UTF-16 character without wchar_t in tests" # else -# ifdef XML_UNICODE -# error "No support for UTF-16 character without wchar_t in tests" -# else -# define XML_FMT_STR "s" -# define xcstrlen(s) strlen(s) -# define xcstrcmp(s, t) strcmp((s), (t)) -# define xcstrncmp(s, t, n) strncmp((s), (t), (n)) -# define XCS(s) s -# endif /* XML_UNICODE */ -# endif /* XML_UNICODE_WCHAR_T */ +# define XML_FMT_STR "s" +# define xcstrlen(s) strlen(s) +# define xcstrcmp(s, t) strcmp((s), (t)) +# define xcstrncmp(s, t, n) strncmp((s), (t), (n)) +# define XCS(s) s +# endif /* XML_UNICODE */ +#endif /* XML_UNICODE_WCHAR_T */ extern XML_Parser g_parser; @@ -98,7 +94,7 @@ extern void basic_teardown(void); extern void _xml_failure(XML_Parser parser, const char *file, int line); -# define xml_failure(parser) _xml_failure((parser), __FILE__, __LINE__) +#define xml_failure(parser) _xml_failure((parser), __FILE__, __LINE__) extern enum XML_Status _XML_Parse_SINGLE_BYTES(XML_Parser parser, const char *s, int len, int isFinal); @@ -107,8 +103,8 @@ extern void _expect_failure(const char *text, enum XML_Error errorCode, const char *errorMessage, const char *file, int lineno); -# define expect_failure(text, errorCode, errorMessage) \ - _expect_failure((text), (errorCode), (errorMessage), __FILE__, __LINE__) +#define expect_failure(text, errorCode, errorMessage) \ + _expect_failure((text), (errorCode), (errorMessage), __FILE__, __LINE__) /* Support functions for handlers to collect up character and attribute data. */ @@ -116,14 +112,14 @@ extern void _expect_failure(const char *text, enum XML_Error errorCode, extern void _run_character_check(const char *text, const XML_Char *expected, const char *file, int line); -# define run_character_check(text, expected) \ - _run_character_check(text, expected, __FILE__, __LINE__) +#define run_character_check(text, expected) \ + _run_character_check(text, expected, __FILE__, __LINE__) extern void _run_attribute_check(const char *text, const XML_Char *expected, const char *file, int line); -# define run_attribute_check(text, expected) \ - _run_attribute_check(text, expected, __FILE__, __LINE__) +#define run_attribute_check(text, expected) \ + _run_attribute_check(text, expected, __FILE__, __LINE__) typedef struct ExtTest { const char *parse_text; @@ -135,11 +131,11 @@ extern void _run_ext_character_check(const char *text, ExtTest *test_data, const XML_Char *expected, const char *file, int line); -# define run_ext_character_check(text, test_data, expected) \ - _run_ext_character_check(text, test_data, expected, __FILE__, __LINE__) +#define run_ext_character_check(text, test_data, expected) \ + _run_ext_character_check(text, test_data, expected, __FILE__, __LINE__) -# define ALLOC_ALWAYS_SUCCEED (-1) -# define REALLOC_ALWAYS_SUCCEED (-1) +#define ALLOC_ALWAYS_SUCCEED (-1) +#define REALLOC_ALWAYS_SUCCEED (-1) extern int g_allocation_count; extern int g_reallocation_count; @@ -151,7 +147,3 @@ extern void *duff_reallocator(void *ptr, size_t size); extern char *portable_strndup(const char *s, size_t n); #endif /* XML_COMMON_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/dummy.h b/tests/dummy.h index 9a75b6fe9244..c1b4056f60c7 100644 --- a/tests/dummy.h +++ b/tests/dummy.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -42,31 +42,27 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_DUMMY_H -# define XML_DUMMY_H +#define XML_DUMMY_H -# define DUMMY_START_DOCTYPE_HANDLER_FLAG (1UL << 0) -# define DUMMY_END_DOCTYPE_HANDLER_FLAG (1UL << 1) -# define DUMMY_ENTITY_DECL_HANDLER_FLAG (1UL << 2) -# define DUMMY_NOTATION_DECL_HANDLER_FLAG (1UL << 3) -# define DUMMY_ELEMENT_DECL_HANDLER_FLAG (1UL << 4) -# define DUMMY_ATTLIST_DECL_HANDLER_FLAG (1UL << 5) -# define DUMMY_COMMENT_HANDLER_FLAG (1UL << 6) -# define DUMMY_PI_HANDLER_FLAG (1UL << 7) -# define DUMMY_START_ELEMENT_HANDLER_FLAG (1UL << 8) -# define DUMMY_START_CDATA_HANDLER_FLAG (1UL << 9) -# define DUMMY_END_CDATA_HANDLER_FLAG (1UL << 10) -# define DUMMY_UNPARSED_ENTITY_DECL_HANDLER_FLAG (1UL << 11) -# define DUMMY_START_NS_DECL_HANDLER_FLAG (1UL << 12) -# define DUMMY_END_NS_DECL_HANDLER_FLAG (1UL << 13) -# define DUMMY_START_DOCTYPE_DECL_HANDLER_FLAG (1UL << 14) -# define DUMMY_END_DOCTYPE_DECL_HANDLER_FLAG (1UL << 15) -# define DUMMY_SKIP_HANDLER_FLAG (1UL << 16) -# define DUMMY_DEFAULT_HANDLER_FLAG (1UL << 17) +#define DUMMY_START_DOCTYPE_HANDLER_FLAG (1UL << 0) +#define DUMMY_END_DOCTYPE_HANDLER_FLAG (1UL << 1) +#define DUMMY_ENTITY_DECL_HANDLER_FLAG (1UL << 2) +#define DUMMY_NOTATION_DECL_HANDLER_FLAG (1UL << 3) +#define DUMMY_ELEMENT_DECL_HANDLER_FLAG (1UL << 4) +#define DUMMY_ATTLIST_DECL_HANDLER_FLAG (1UL << 5) +#define DUMMY_COMMENT_HANDLER_FLAG (1UL << 6) +#define DUMMY_PI_HANDLER_FLAG (1UL << 7) +#define DUMMY_START_ELEMENT_HANDLER_FLAG (1UL << 8) +#define DUMMY_START_CDATA_HANDLER_FLAG (1UL << 9) +#define DUMMY_END_CDATA_HANDLER_FLAG (1UL << 10) +#define DUMMY_UNPARSED_ENTITY_DECL_HANDLER_FLAG (1UL << 11) +#define DUMMY_START_NS_DECL_HANDLER_FLAG (1UL << 12) +#define DUMMY_END_NS_DECL_HANDLER_FLAG (1UL << 13) +#define DUMMY_START_DOCTYPE_DECL_HANDLER_FLAG (1UL << 14) +#define DUMMY_END_DOCTYPE_DECL_HANDLER_FLAG (1UL << 15) +#define DUMMY_SKIP_HANDLER_FLAG (1UL << 16) +#define DUMMY_DEFAULT_HANDLER_FLAG (1UL << 17) extern void init_dummy_handlers(void); extern unsigned long get_dummy_handler_flags(void); @@ -146,7 +142,3 @@ extern void XMLCALL dummy_skip_handler(void *userData, int is_parameter_entity); #endif /* XML_DUMMY_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/handlers.h b/tests/handlers.h index 02c170d226d2..a0b413140a7c 100644 --- a/tests/handlers.h +++ b/tests/handlers.h @@ -45,16 +45,12 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_HANDLERS_H -# define XML_HANDLERS_H +#define XML_HANDLERS_H -# include "expat_config.h" +#include "expat_config.h" -# include "expat.h" +#include "expat.h" /* Variable holding the expected handler userData */ extern const void *g_handler_data; @@ -74,8 +70,8 @@ extern void XMLCALL start_element_event_handler(void *userData, extern void XMLCALL end_element_event_handler(void *userData, const XML_Char *name); -# define STRUCT_START_TAG 0 -# define STRUCT_END_TAG 1 +#define STRUCT_START_TAG 0 +#define STRUCT_END_TAG 1 extern void XMLCALL start_element_event_handler2(void *userData, const XML_Char *name, @@ -516,22 +512,21 @@ extern const struct handler_record_entry * _handler_record_get(const struct handler_record_list *storage, int index, const char *file, int line); -# define handler_record_get(storage, index) \ - _handler_record_get((storage), (index), __FILE__, __LINE__) +#define handler_record_get(storage, index) \ + _handler_record_get((storage), (index), __FILE__, __LINE__) -# define assert_record_handler_called(storage, index, expected_name, \ - expected_arg) \ - do { \ - const struct handler_record_entry *e \ - = handler_record_get(storage, index); \ - assert_true(strcmp(e->name, expected_name) == 0); \ - assert_true(e->arg == (expected_arg)); \ - } while (0) +#define assert_record_handler_called(storage, index, expected_name, \ + expected_arg) \ + do { \ + const struct handler_record_entry *e = handler_record_get(storage, index); \ + assert_true(strcmp(e->name, expected_name) == 0); \ + assert_true(e->arg == (expected_arg)); \ + } while (0) /* Entity Declaration Handlers */ -# define ENTITY_MATCH_FAIL (-1) -# define ENTITY_MATCH_NOT_FOUND (0) -# define ENTITY_MATCH_SUCCESS (1) +#define ENTITY_MATCH_FAIL (-1) +#define ENTITY_MATCH_NOT_FOUND (0) +#define ENTITY_MATCH_SUCCESS (1) extern void XMLCALL param_entity_match_handler( void *userData, const XML_Char *entityName, int is_parameter_entity, @@ -630,7 +625,3 @@ extern void XMLCALL suspend_then_resume_character_handler(void *userData, int len); #endif /* XML_HANDLERS_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/hash_tests.c b/tests/hash_tests.c new file mode 100644 index 000000000000..5c5d7d4cf0f0 --- /dev/null +++ b/tests/hash_tests.c @@ -0,0 +1,157 @@ +/* Tests related to the hash tables used inside Expat +__ __ _ + ___\ \/ /_ __ __ _| |_ + / _ \\ /| '_ \ / _` | __| + | __// \| |_) | (_| | |_ + \___/_/\_\ .__/ \__,_|\__| + |_| XML parser + + Copyright (c) 2026 Sebastian Pipping <sebastian@pipping.org> + 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 "hash_tests.h" + +#include "common.h" // for XCS +#include "expat.h" +#include "hash_table.h" +#include "minicheck.h" + +#include <stdbool.h> +#include <string.h> // for memcmp + +START_TEST(test_hash_table) { + // The test is not doing any parsing, so a single run + // (with `g_chunkSize == 0`) is enough + if (g_chunkSize != 0) + return; + + typedef struct { + const XML_Char *name; + bool initialized; + } NAME_AND_FLAG; + + HASH_TABLE table; + XML_Parser parser = XML_ParserCreate(NULL); + hashTableInit(&table, parser); + + const XML_Char *const key1 = XCS("key1"); + const XML_Char *const key2 = XCS("key2"); + const XML_Char *const key3 = XCS("key1overlap"); + + // Self-test: `key3` starts with `key1` but is different from it + assert_true(memcmp(key1, key3, keylen(key1)) == 0); + assert_true(keyeq(key1, keylen(key1), key3) == XML_FALSE); + + // Test: Look up false for all keys because the table is empty + assert_true(lookup(parser, &table, key1, 0) == NULL); + assert_true(lookup(parser, &table, key2, 0) == NULL); + assert_true(lookup(parser, &table, key3, 0) == NULL); + + // Test: Iteration yields 0 items initially + { + HASH_TABLE_ITER iter1; + hashTableIterInit(&iter1, &table); + assert_true(hashTableIterNext(&iter1) == NULL); + } + + // Test: Insertion works (including initialization to zero) + NAME_AND_FLAG *const inserted1 + = (NAME_AND_FLAG *)lookup(parser, &table, key1, sizeof(NAME_AND_FLAG)); + assert_true(inserted1 != NULL); + assert_true(inserted1->name == key1); + assert_true(! inserted1->initialized); + + // Make it possible to tell the struct apart from a freshly inserted version + inserted1->initialized = true; + + // Test: Only present keys can be looked up + assert_true(lookup(parser, &table, key1, 0) != NULL); + assert_true(lookup(parser, &table, key2, 0) == NULL); + assert_true(lookup(parser, &table, key3, 0) == NULL); + + // Test: Key length works without false positives + assert_true(lookupWithLength(parser, &table, key3, /*nameLen=*/3, 0) == NULL); + assert_true(lookupWithLength(parser, &table, key3, /*nameLen=*/4, 0) != NULL); + assert_true(lookupWithLength(parser, &table, key3, /*nameLen=*/5, 0) == NULL); + + // TEST: Lookup does not reset existing entries to zeros + NAME_AND_FLAG *const found + = (NAME_AND_FLAG *)lookup(parser, &table, key1, sizeof(NAME_AND_FLAG)); + assert_true(found != NULL); + assert_true(found->name == key1); + assert_true(found->initialized); // this is key + + // Test: Insertion of a second item works + assert_true(lookup(parser, &table, key2, 0) == NULL); + assert_true(lookup(parser, &table, key2, sizeof(NAME_AND_FLAG)) != NULL); + assert_true(lookup(parser, &table, key2, 0) != NULL); + + // Test: Iteration yields nothing but the two expected items + { + HASH_TABLE_ITER iter2; + hashTableIterInit(&iter2, &table); + size_t itemCount = 0; + while (true) { + const NAME_AND_FLAG *const item + = (const NAME_AND_FLAG *)hashTableIterNext(&iter2); + if (item == NULL) + break; + + itemCount++; + + if (keyeq(key1, keylen(key1), item->name) == XML_TRUE) + assert_true(item->initialized); + else if (keyeq(key2, keylen(key2), item->name) == XML_TRUE) + assert_true(! item->initialized); + else + fail("unexpected item .name"); + } + assert_true(itemCount == 2); + } + + // Test: After clearing all lookups fail + hashTableClear(&table); + assert_true(lookup(parser, &table, key1, 0) == NULL); + assert_true(lookup(parser, &table, key2, 0) == NULL); + assert_true(lookup(parser, &table, key3, 0) == NULL); + + // Test: After clearing iteration yields 0 items again + { + HASH_TABLE_ITER iter3; + hashTableIterInit(&iter3, &table); + assert_true(hashTableIterNext(&iter3) == NULL); + } + + hashTableDestroy(&table); + XML_ParserFree(parser); +} +END_TEST + +void +make_hash_test_case(Suite *s) { + TCase *const tc_hash = tcase_create("hash tests"); + suite_add_tcase(s, tc_hash); + tcase_add_test(tc_hash, test_hash_table); +} diff --git a/tests/hash_tests.h b/tests/hash_tests.h new file mode 100644 index 000000000000..ed2c459ff82a --- /dev/null +++ b/tests/hash_tests.h @@ -0,0 +1,41 @@ +/* Tests related to the hash tables used inside Expat + __ __ _ + ___\ \/ /_ __ __ _| |_ + / _ \\ /| '_ \ / _` | __| + | __// \| |_) | (_| | |_ + \___/_/\_\ .__/ \__,_|\__| + |_| XML parser + + Copyright (c) 2026 Sebastian Pipping <sebastian@pipping.org> + 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_HASH_TESTS_H +#define XML_HASH_TESTS_H + +#include "minicheck.h" + +extern void make_hash_test_case(Suite *s); + +#endif /* XML_HASH_TESTS_H */ diff --git a/tests/memcheck.c b/tests/memcheck.c index b670b322c012..8361f78fb356 100644 --- a/tests/memcheck.c +++ b/tests/memcheck.c @@ -10,6 +10,7 @@ Copyright (c) 2017-2023 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2022 Sean McBride <sean@rogue-research.com> Copyright (c) 2026 Matthew Fernandez <matthew.fernandez@gmail.com> + Copyright (c) 2026 Leo Camus <leo.camus23@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -117,7 +118,7 @@ tracking_free(void *ptr) { if (entry->next != NULL) entry->next->prev = entry->prev; else - alloc_tail = entry->next; + alloc_tail = entry->prev; free(entry); } else { printf("Attempting to free unallocated memory at %p\n", ptr); diff --git a/tests/memcheck.h b/tests/memcheck.h index 7255aba14796..3febafe2fa05 100644 --- a/tests/memcheck.h +++ b/tests/memcheck.h @@ -7,8 +7,8 @@ \___/_/\_\ .__/ \__,_|\__| |_| XML parser - Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> - Copyright (c) 2017 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> + Copyright (c) 2017-2026 Sebastian Pipping <sebastian@pipping.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -33,10 +33,6 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_MEMCHECK_H # define XML_MEMCHECK_H 1 @@ -53,7 +49,3 @@ void *tracking_realloc(void *ptr, size_t size); int tracking_report(void); #endif /* XML_MEMCHECK_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/minicheck.h b/tests/minicheck.h index d80c8603c637..0b21f5f581d5 100644 --- a/tests/minicheck.h +++ b/tests/minicheck.h @@ -14,7 +14,7 @@ Copyright (c) 2004-2006 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2006-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com> Licensed under the MIT license: @@ -41,27 +41,15 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_MINICHECK_H -# define XML_MINICHECK_H - -# define CK_NOFORK 0 -# define CK_FORK 1 +#define XML_MINICHECK_H -# define CK_SILENT 0 -# define CK_NORMAL 1 -# define CK_VERBOSE 2 +#define CK_NOFORK 0 +#define CK_FORK 1 -/* Workaround for Microsoft's compiler and Tru64 Unix systems where the - C compiler has a working __func__, but the C++ compiler only has a - working __FUNCTION__. This could be fixed in configure.in, but it's - not worth it right now. */ -# if defined(_MSC_VER) || (defined(__osf__) && defined(__cplusplus)) -# define __func__ __FUNCTION__ -# endif +#define CK_SILENT 0 +#define CK_NORMAL 1 +#define CK_VERBOSE 2 /* PRINTF_LIKE has two effects: 1. Make clang's -Wformat-nonliteral stop warning about non-literal format @@ -69,30 +57,30 @@ extern "C" { 2. Make both clang and gcc's -Wformat-nonliteral warn about *callers* of the annotated function that use a non-literal format string. */ -# if defined(__GNUC__) -# define PRINTF_LIKE(fmtpos, argspos) \ - __attribute__((format(printf, fmtpos, argspos))) -# else -# define PRINTF_LIKE(fmtpos, argspos) -# endif +#if defined(__GNUC__) +# define PRINTF_LIKE(fmtpos, argspos) \ + __attribute__((format(printf, fmtpos, argspos))) +#else +# define PRINTF_LIKE(fmtpos, argspos) +#endif -# define START_TEST(testname) \ - static void testname(void) { \ - _check_set_test_info(__func__, __FILE__, __LINE__); \ - { -# define END_TEST \ - } \ - } +#define START_TEST(testname) \ + static void testname(void) { \ + _check_set_test_info(__func__, __FILE__, __LINE__); \ + { +#define END_TEST \ + } \ + } void PRINTF_LIKE(1, 2) set_subtest(char const *fmt, ...); -# define fail(msg) _fail(__FILE__, __LINE__, msg) -# define assert_true(cond) \ - do { \ - if (! (cond)) { \ - _fail(__FILE__, __LINE__, "check failed: " #cond); \ - } \ - } while (0) +#define fail(msg) _fail(__FILE__, __LINE__, msg) +#define assert_true(cond) \ + do { \ + if (! (cond)) { \ + _fail(__FILE__, __LINE__, "check failed: " #cond); \ + } \ + } while (0) typedef void (*tcase_setup_function)(void); typedef void (*tcase_teardown_function)(void); @@ -131,11 +119,11 @@ void _check_set_test_info(char const *function, char const *filename, * Prototypes for the actual implementation. */ -# if defined(__has_attribute) -# if __has_attribute(noreturn) +#if defined(__has_attribute) +# if __has_attribute(noreturn) __attribute__((noreturn)) -# endif # endif +#endif void _fail(const char *file, int line, const char *msg); Suite *suite_create(const char *name); TCase *tcase_create(const char *name); @@ -150,7 +138,3 @@ int srunner_ntests_failed(SRunner *runner); void srunner_free(SRunner *runner); #endif /* XML_MINICHECK_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/misc_tests.c b/tests/misc_tests.c index 82b4b54b15bf..717cc2dd31aa 100644 --- a/tests/misc_tests.c +++ b/tests/misc_tests.c @@ -23,6 +23,7 @@ Copyright (c) 2026 Matthew Fernandez <matthew.fernandez@gmail.com> Copyright (c) 2026 Kartik Kenchi <netliomax25@gmail.com> Copyright (c) 2026 Evgeny Kotkov <kotkov@apache.org> + Copyright (c) 2026 Darren Carreras <carrerasdarren@gmail.com> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -217,7 +218,7 @@ START_TEST(test_misc_version) { if (! versions_equal(&read_version, &parsed_version)) fail("Version mismatch"); - if (xcstrcmp(version_text, XCS("expat_2.8.3")) + if (xcstrcmp(version_text, XCS("expat_2.8.5")) != 0) /* needs bump on releases */ fail("XML_*_VERSION in expat.h out of sync?\n"); } @@ -839,6 +840,60 @@ START_TEST(test_misc_resume_parser_forbidden_from_handler) { } END_TEST +typedef struct { + XML_Parser parser; + int converterCallCount; + int releaseCallCount; +} EncodingCallbackData; + +static int XMLCALL +reentrant_encoding_converter(void *userData, const char *s) { + EncodingCallbackData *const data = userData; + UNUSED_P(s); + data->converterCallCount++; + forbidden_calls_character_handler(data->parser, NULL, 0); + return 'A'; +} + +static void XMLCALL +reentrant_encoding_release(void *userData) { + EncodingCallbackData *const data = userData; + data->releaseCallCount++; + forbidden_calls_character_handler(data->parser, NULL, 0); +} + +static int XMLCALL +reentrant_encoding_handler(void *userData, const XML_Char *name, + XML_Encoding *info) { + EncodingCallbackData *const data = userData; + UNUSED_P(name); + + for (int i = 0; i < 256; i++) + info->map[i] = i; + info->map[0x80] = -2; // Route byte 0x80 through the custom converter. + info->data = data; + info->convert = reentrant_encoding_converter; + info->release = reentrant_encoding_release; + return XML_STATUS_OK; +} + +START_TEST(test_misc_unknown_encoding_callbacks_protected) { + const char *const doc + = "<?xml version='1.0' encoding='reentrant-conv'?><doc>\x80\x80</doc>"; + XML_Parser parser = XML_ParserCreate(NULL); + EncodingCallbackData data = {parser, 0, 0}; + XML_SetUnknownEncodingHandler(parser, reentrant_encoding_handler, &data); + + assert_true(XML_Parse(parser, doc, (int)strlen(doc), /*isFinal=*/XML_TRUE) + == XML_STATUS_OK); + assert_true(data.converterCallCount > 0); + assert_true(data.releaseCallCount == 0); // Released by XML_ParserFree below. + + XML_ParserFree(parser); + assert_true(data.releaseCallCount == 1); +} +END_TEST + // General attack payload idea by Jason Kratzer of Mozilla START_TEST(test_misc_low_surrogate_mozilla_bug_2053153) { const char doc_before[] = "<\0!\0D\0O\0C\0T\0Y\0P\0E\0 \0d\0 \0[\0\n\0" @@ -936,6 +991,7 @@ make_miscellaneous_test_case(Suite *s) { tcase_add_test(tc_misc, test_misc_no_infinite_loop_issue_1161); tcase_add_test(tc_misc, test_misc_calls_forbidden_from_handlers); tcase_add_test(tc_misc, test_misc_resume_parser_forbidden_from_handler); + tcase_add_test(tc_misc, test_misc_unknown_encoding_callbacks_protected); tcase_add_test(tc_misc, test_misc_input_2gb); tcase_add_test(tc_misc, test_misc_low_surrogate_mozilla_bug_2053153); } diff --git a/tests/misc_tests.h b/tests/misc_tests.h index 4cff492e4c90..8f8cda353ced 100644 --- a/tests/misc_tests.h +++ b/tests/misc_tests.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -42,17 +42,9 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_MISC_TESTS_H -# define XML_MISC_TESTS_H +#define XML_MISC_TESTS_H extern void make_miscellaneous_test_case(Suite *s); #endif /* XML_MISC_TESTS_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/ns_tests.h b/tests/ns_tests.h index 72d13532e61e..9c3640b1c658 100644 --- a/tests/ns_tests.h +++ b/tests/ns_tests.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -42,17 +42,9 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_NS_TESTS_H -# define XML_NS_TESTS_H +#define XML_NS_TESTS_H extern void make_namespace_test_case(Suite *s); #endif /* XML_NS_TESTS_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/nsalloc_tests.c b/tests/nsalloc_tests.c index b8c361ae25df..a0ee114efe3f 100644 --- a/tests/nsalloc_tests.c +++ b/tests/nsalloc_tests.c @@ -43,6 +43,8 @@ SPDX-License-Identifier: MIT */ +#include "expat_config.h" + #if defined(NDEBUG) # undef NDEBUG /* because test suite relies on assert(...) at the moment */ #endif diff --git a/tests/nsalloc_tests.h b/tests/nsalloc_tests.h index 59ec6c559847..483926b1428d 100644 --- a/tests/nsalloc_tests.h +++ b/tests/nsalloc_tests.h @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2022 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -42,17 +42,9 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_NSALLOC_TESTS_H -# define XML_NSALLOC_TESTS_H +#define XML_NSALLOC_TESTS_H extern void make_nsalloc_test_case(Suite *s); #endif /* XML_NSALLOC_TESTS_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/runtests.c b/tests/runtests.c index 51fd131e99f9..12f8c36c12e3 100644 --- a/tests/runtests.c +++ b/tests/runtests.c @@ -10,7 +10,7 @@ Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2023 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2017 Joe Orton <jorton@redhat.com> Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com> @@ -55,6 +55,7 @@ #include "common.h" #include "basic_tests.h" +#include "hash_tests.h" #include "ns_tests.h" #include "misc_tests.h" #include "alloc_tests.h" @@ -68,6 +69,7 @@ make_suite(void) { Suite *s = suite_create("basic"); make_basic_test_case(s); + make_hash_test_case(s); make_namespace_test_case(s); make_miscellaneous_test_case(s); make_alloc_test_case(s); diff --git a/tests/structdata.h b/tests/structdata.h index 2fb9fca714c1..a1f6dcbe7e61 100644 --- a/tests/structdata.h +++ b/tests/structdata.h @@ -8,6 +8,7 @@ |_| XML parser Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> + Copyright (c) 2026 Sebastian Pipping <sebastian@pipping.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -32,10 +33,6 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - #ifndef XML_STRUCTDATA_H # define XML_STRUCTDATA_H 1 @@ -65,7 +62,3 @@ void StructData_CheckItems(StructData *storage, const StructDataEntry *expected, void StructData_Dispose(StructData *storage); #endif /* XML_STRUCTDATA_H */ - -#ifdef __cplusplus -} -#endif diff --git a/tests/xmltest.sh b/tests/xmltest.sh index 56e66c56f6ea..423b773a8860 100755 --- a/tests/xmltest.sh +++ b/tests/xmltest.sh @@ -31,6 +31,7 @@ # Copyright (c) 2008-2019 Sebastian Pipping <sebastian@pipping.org> # Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> # Copyright (c) 2025 Hanno Böck <hanno@gentoo.org> +# Copyright (c) 2026 Huang Wenbin <huangwenbin@kylinos.cn> # Licensed under the MIT license: # # Permission is hereby granted, free of charge, to any person obtaining @@ -172,3 +173,4 @@ done echo "Passed: $SUCCESS" echo "Failed: $ERROR" +test "$ERROR" -eq 0 diff --git a/xmlwf/Makefile.in b/xmlwf/Makefile.in index d6d4033b0090..7c4ff12ef536 100644 --- a/xmlwf/Makefile.in +++ b/xmlwf/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.18.1 from Makefile.am. +# Makefile.in generated by automake 1.19 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2025 Free Software Foundation, Inc. +# Copyright (C) 1994-2026 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -335,6 +335,7 @@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__rm_f_notfound = @am__rm_f_notfound@ am__tar = @am__tar@ +am__tar_ignore_stderr = @am__tar_ignore_stderr@ am__untar = @am__untar@ am__xargs_n = @am__xargs_n@ bindir = @bindir@ @@ -412,9 +413,9 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__confi exit 1;; \ esac; \ done; \ - echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu xmlwf/Makefile'; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign xmlwf/Makefile'; \ $(am__cd) $(top_srcdir) && \ - $(AUTOMAKE) --gnu xmlwf/Makefile + $(AUTOMAKE) --foreign xmlwf/Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ @@ -499,22 +500,25 @@ $(am__depfiles_remade): am--depfiles: $(am__depfiles_remade) .c.o: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< .c.obj: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .c.lo: -@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo @AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< @@ -639,13 +643,14 @@ distdir: $(BUILT_SOURCES) distdir-am: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + am__distdir="$(distdir)"; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ - sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sed "/\//!d;s|^|$$am__distdir/|;s,/[^/]*$$,," | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ diff --git a/xmlwf/unixfilemap.c b/xmlwf/unixfilemap.c index 5ebffe35ba05..71fb744b7a53 100644 --- a/xmlwf/unixfilemap.c +++ b/xmlwf/unixfilemap.c @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net> Copyright (c) 2001-2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2006 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2026 Matthew Fernandez <matthew.fernandez@gmail.com> Licensed under the MIT license: @@ -37,6 +37,8 @@ SPDX-License-Identifier: MIT */ +#include "expat_config.h" // for off_t + #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> diff --git a/xmlwf/xmlfile.c b/xmlwf/xmlfile.c index 0b5f5d67298c..85e5dc582827 100644 --- a/xmlwf/xmlfile.c +++ b/xmlwf/xmlfile.c @@ -11,7 +11,7 @@ Copyright (c) 2002-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2004-2006 Karl Waclawek <karl@waclawek.net> Copyright (c) 2005-2007 Steven Solie <steven@solie.ca> - Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> Copyright (c) 2019 David Loffredo <loffredo@steptools.com> Copyright (c) 2021 Donghee Na <donghee.na@python.org> @@ -266,7 +266,7 @@ externalEntityRefStream(XML_Parser parser, const XML_Char *context, return ret; } -int +bool XML_ProcessFile(XML_Parser parser, const XML_Char *filename, unsigned flags) { int result; @@ -299,5 +299,5 @@ XML_ProcessFile(XML_Parser parser, const XML_Char *filename, unsigned flags) { } } else result = processStream(filename, parser); - return result; + return (bool)result; } diff --git a/xmlwf/xmlfile.h b/xmlwf/xmlfile.h index c4c8c82cd5cb..7eb4811103c2 100644 --- a/xmlwf/xmlfile.h +++ b/xmlwf/xmlfile.h @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper <coopercc@users.sourceforge.net> Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> Copyright (c) 2005 Karl Waclawek <karl@waclawek.net> - Copyright (c) 2016-2023 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -35,6 +35,8 @@ SPDX-License-Identifier: MIT */ +#include <stdbool.h> + #define XML_MAP_FILE 01 #define XML_EXTERNAL_ENTITIES 02 @@ -46,5 +48,5 @@ extern int g_read_size_bytes; -extern int XML_ProcessFile(XML_Parser parser, const XML_Char *filename, - unsigned flags); +extern bool XML_ProcessFile(XML_Parser parser, const XML_Char *filename, + unsigned flags); diff --git a/xmlwf/xmlmime.h b/xmlwf/xmlmime.h index 87665bf6cd9d..335a87e9cbc4 100644 --- a/xmlwf/xmlmime.h +++ b/xmlwf/xmlmime.h @@ -8,7 +8,7 @@ Copyright (c) 1997-2000 Thai Open Source Software Center Ltd Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> - Copyright (c) 2016-2017 Sebastian Pipping <sebastian@pipping.org> + Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org> Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -33,10 +33,6 @@ SPDX-License-Identifier: MIT */ -#ifdef __cplusplus -extern "C" { -#endif - /* Registered charset names are at most 40 characters long. */ #define CHARSET_MAX 41 @@ -48,7 +44,3 @@ extern "C" { */ void getXMLCharset(const char *buf, char *charset); - -#ifdef __cplusplus -} -#endif diff --git a/xmlwf/xmlwf.c b/xmlwf/xmlwf.c index 57beef1f75c6..892732cb0729 100644 --- a/xmlwf/xmlwf.c +++ b/xmlwf/xmlwf.c @@ -933,7 +933,7 @@ usage(const XML_Char *prog, int rc) { T(" -w enable support for [W]indows code pages\n") T(" -r disable memory-mapping and use [r]ead calls instead\n") T(" -g BYTES buffer size to request per call pair to XML_[G]etBuffer and read (default: 8 KiB)\n") - T(" -k when processing multiple files, [k]eep processing after first file with error\n") + T(" -k when processing multiple files, [k]eep processing after first file with error, and make the last error determine the exit code.\n") T("\n") T("output control arguments:\n") T(" -d DIRECTORY output [d]estination directory\n") @@ -969,7 +969,7 @@ usage(const XML_Char *prog, int rc) { T(" 0 the input files are well-formed and the output (if requested) was written successfully\n") T(" 1 could not allocate data structures, signals a serious problem with execution environment\n") T(" 2 one or more input files were not well-formed\n") - T(" 3 could not create an output file\n") + T(" 3 one or more output files could not be written\n") T(" 4 command-line argument error\n") T("\n") T("xmlwf of libexpat is software libre, licensed under the MIT license.\n") @@ -1194,7 +1194,6 @@ tmain(int argc, XML_Char **argv) { } for (; i < argc; i++) { XML_Char *outName = 0; - int result; XML_Parser parser; if (useNamespaces) parser = XML_ParserCreateNS(encoding, NSSEP); @@ -1350,19 +1349,29 @@ tmain(int argc, XML_Char **argv) { } if (windowsCodePages) XML_SetUnknownEncodingHandler(parser, unknownEncoding, 0); - result = XML_ProcessFile(parser, useStdin ? NULL : argv[i], processFlags); + const bool processingSuccess + = XML_ProcessFile(parser, useStdin ? NULL : argv[i], processFlags); + bool closingSuccess = true; if (outputDir) { if (outputType == 'm') metaEndDocument(parser); - fclose(userData.fp); - if (! result) { + closingSuccess = (fclose(userData.fp) == 0); + if (! closingSuccess) + tperror(outName); + if (! processingSuccess || ! closingSuccess) { tremove(outName); } free(outName); } XML_ParserFree(parser); - if (! result) { - exitCode = XMLWF_EXIT_NOT_WELLFORMED; + if (! processingSuccess || ! closingSuccess) { + // NOTE: If both failed, the last error should determine the exit code. + // Failure to close happened after failure to process. + if (! closingSuccess) + exitCode = XMLWF_EXIT_OUTPUT_ERROR; + else if (! processingSuccess) + exitCode = XMLWF_EXIT_NOT_WELLFORMED; + cleanupUserData(&userData); if (! continueOnError) { break; diff --git a/xmlwf/xmlwf_helpgen.py b/xmlwf/xmlwf_helpgen.py index d80020550273..98a01e4b282d 100755 --- a/xmlwf/xmlwf_helpgen.py +++ b/xmlwf/xmlwf_helpgen.py @@ -50,7 +50,7 @@ epilog = dedent( 0 the input files are well-formed and the output (if requested) was written successfully 1 could not allocate data structures, signals a serious problem with execution environment 2 one or more input files were not well-formed - 3 could not create an output file + 3 one or more output files could not be written 4 command-line argument error xmlwf of libexpat is software libre, licensed under the MIT license. @@ -116,7 +116,10 @@ input_related.add_argument( input_related.add_argument( "-k", action="store_true", - help="when processing multiple files, [k]eep processing after first file with error", + help=( + "when processing multiple files, [k]eep processing after first file with error" + ", and make the last error determine the exit code." + ), ) output_related = parser.add_argument_group("output control arguments") diff --git a/xmlwf/xmlwf_helpgen.sh b/xmlwf/xmlwf_helpgen.sh index ac3c33ea00b5..a4ebb6db4e8b 100755 --- a/xmlwf/xmlwf_helpgen.sh +++ b/xmlwf/xmlwf_helpgen.sh @@ -6,7 +6,7 @@ # \___/_/\_\ .__/ \__,_|\__| # |_| XML parser # -# Copyright (c) 2019-2021 Sebastian Pipping <sebastian@pipping.org> +# Copyright (c) 2019-2026 Sebastian Pipping <sebastian@pipping.org> # Licensed under the MIT license: # # Permission is hereby granted, free of charge, to any person obtaining @@ -30,7 +30,11 @@ # # SPDX-License-Identifier: MIT -./xmlwf/xmlwf_helpgen.py | sed \ +set -e + +cd "$(dirname "$(type -P "$0")")" + +./xmlwf_helpgen.py | sed \ -e 's,usage: ,usage:,' \ -e 's, xmlwf, %s,' \ -e 's, \[-h | -v\],,' \ |
