浏览代码

Merge branch 'thirdparty' into thirdparty_dev

Source commit: 1e7d0f2c60ccb216a9986a8120545067aeb06593
Martin Prikryl 6 月之前
父节点
当前提交
c83bdb1f32

+ 6 - 6
libs/expat/CMake.README

@@ -3,25 +3,25 @@
 The cmake based buildsystem for expat works on Windows (cygwin, mingw, Visual
 The cmake based buildsystem for expat works on Windows (cygwin, mingw, Visual
 Studio) and should work on all other platform cmake supports.
 Studio) and should work on all other platform cmake supports.
 
 
-Assuming ~/expat-2.7.0 is the source directory of expat, add a subdirectory
+Assuming ~/expat-2.7.1 is the source directory of expat, add a subdirectory
 build and change into that directory:
 build and change into that directory:
-~/expat-2.7.0$ mkdir build && cd build
-~/expat-2.7.0/build$
+~/expat-2.7.1$ mkdir build && cd build
+~/expat-2.7.1/build$
 
 
 From that directory, call cmake first, then call make, make test and
 From that directory, call cmake first, then call make, make test and
 make install in the usual way:
 make install in the usual way:
-~/expat-2.7.0/build$ cmake ..
+~/expat-2.7.1/build$ cmake ..
 -- The C compiler identification is GNU
 -- The C compiler identification is GNU
 -- The CXX compiler identification is GNU
 -- The CXX compiler identification is GNU
 ....
 ....
 -- Configuring done
 -- Configuring done
 -- Generating done
 -- Generating done
--- Build files have been written to: /home/patrick/expat-2.7.0/build
+-- Build files have been written to: /home/patrick/expat-2.7.1/build
 
 
 If you want to specify the install location for your files, append
 If you want to specify the install location for your files, append
 -DCMAKE_INSTALL_PREFIX=/your/install/path to the cmake call.
 -DCMAKE_INSTALL_PREFIX=/your/install/path to the cmake call.
 
 
-~/expat-2.7.0/build$ make && make test && make install
+~/expat-2.7.1/build$ make && make test && make install
 Scanning dependencies of target expat
 Scanning dependencies of target expat
 [  5%] Building C object CMakeFiles/expat.dir/lib/xmlparse.c.o
 [  5%] Building C object CMakeFiles/expat.dir/lib/xmlparse.c.o
 [ 11%] Building C object CMakeFiles/expat.dir/lib/xmlrole.c.o
 [ 11%] Building C object CMakeFiles/expat.dir/lib/xmlrole.c.o

+ 2 - 2
libs/expat/CMakeLists.txt

@@ -39,7 +39,7 @@ cmake_minimum_required(VERSION 3.13.0)
 
 
 project(expat
 project(expat
     VERSION
     VERSION
-        2.7.0
+        2.7.1
     LANGUAGES
     LANGUAGES
         C
         C
 )
 )
@@ -472,7 +472,7 @@ foreach(build_type_upper
 endforeach()
 endforeach()
 
 
 set(LIBCURRENT 11)  # sync
 set(LIBCURRENT 11)  # sync
-set(LIBREVISION 1)  # with
+set(LIBREVISION 2)  # with
 set(LIBAGE 10)      # configure.ac!
 set(LIBAGE 10)      # configure.ac!
 math(EXPR LIBCURRENT_MINUS_AGE "${LIBCURRENT} - ${LIBAGE}")
 math(EXPR LIBCURRENT_MINUS_AGE "${LIBCURRENT} - ${LIBAGE}")
 
 

+ 1 - 1
libs/expat/COPYING

@@ -1,5 +1,5 @@
 Copyright (c) 1998-2000 Thai Open Source Software Center Ltd and Clark Cooper
 Copyright (c) 1998-2000 Thai Open Source Software Center Ltd and Clark Cooper
-Copyright (c) 2001-2022 Expat maintainers
+Copyright (c) 2001-2025 Expat maintainers
 
 
 Permission is hereby granted, free of charge, to any person obtaining
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
 a copy of this software and associated documentation files (the

+ 38 - 0
libs/expat/Changes

@@ -37,6 +37,44 @@
 !! THANK YOU!                        Sebastian Pipping -- Berlin, 2024-03-09 !!
 !! THANK YOU!                        Sebastian Pipping -- Berlin, 2024-03-09 !!
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
 
+Release 2.7.1 Thu March 27 2025
+        Bug fixes:
+       #980 #989  Restore event pointer behavior from Expat 2.6.4
+                    (that the fix to CVE-2024-8176 changed in 2.7.0);
+                    affected API functions are:
+                    - XML_GetCurrentByteCount
+                    - XML_GetCurrentByteIndex
+                    - XML_GetCurrentColumnNumber
+                    - XML_GetCurrentLineNumber
+                    - XML_GetInputContext
+
+        Other changes:
+       #976 #977  Autotools: Integrate files "fuzz/xml_lpm_fuzzer.{cpp,proto}"
+                    with Automake that were missing from 2.7.0 release tarballs
+       #983 #984  Fix printf format specifiers for 32bit Emscripten
+            #992  docs: Promote OpenSSF Best Practices self-certification
+            #978  tests/benchmark: Resolve mistaken double close
+            #986  Address compiler warnings
+       #990 #993  Version info bumped from 11:1:10 (libexpat*.so.1.10.1)
+                    to 11:2:10 (libexpat*.so.1.10.2); see https://verbump.de/
+                    for what these numbers do
+
+        Infrastructure:
+            #982  CI: Start running Perl XML::Parser integration tests
+            #987  CI: Enforce Clang Static Analyzer clean code
+            #991  CI: Re-enable warning clang-analyzer-valist.Uninitialized
+                    for clang-tidy
+            #981  CI: Cover compilation with musl
+       #983 #984  CI: Cover compilation with 32bit Emscripten
+       #976 #977  CI: Protect against fuzzer files missing from future
+                    release archives
+
+        Special thanks to:
+            Berkay Eren Ürün
+            Matthew Fernandez
+                 and
+            Perl XML::Parser
+
 Release 2.7.0 Thu March 13 2025
 Release 2.7.0 Thu March 13 2025
         Security fixes:
         Security fixes:
        #893 #973  CVE-2024-8176 -- Fix crash from chaining a large number
        #893 #973  CVE-2024-8176 -- Fix crash from chaining a large number

+ 3 - 1
libs/expat/Makefile.am

@@ -6,7 +6,7 @@
 #                      \___/_/\_\ .__/ \__,_|\__|
 #                      \___/_/\_\ .__/ \__,_|\__|
 #                               |_| XML parser
 #                               |_| XML parser
 #
 #
-# Copyright (c) 2017-2023 Sebastian Pipping <[email protected]>
+# Copyright (c) 2017-2025 Sebastian Pipping <[email protected]>
 # Copyright (c) 2018      KangLin <[email protected]>
 # Copyright (c) 2018      KangLin <[email protected]>
 # Copyright (c) 2022      Johnny Jazeix <[email protected]>
 # Copyright (c) 2022      Johnny Jazeix <[email protected]>
 # Copyright (c) 2023      Sony Corporation / Snild Dolkow <[email protected]>
 # Copyright (c) 2023      Sony Corporation / Snild Dolkow <[email protected]>
@@ -96,6 +96,8 @@ EXTRA_DIST = \
     conftools/expat.m4 \
     conftools/expat.m4 \
     conftools/get-version.sh \
     conftools/get-version.sh \
     \
     \
+    fuzz/xml_lpm_fuzzer.cpp \
+    fuzz/xml_lpm_fuzzer.proto \
     fuzz/xml_parsebuffer_fuzzer.c \
     fuzz/xml_parsebuffer_fuzzer.c \
     fuzz/xml_parse_fuzzer.c \
     fuzz/xml_parse_fuzzer.c \
     \
     \

+ 3 - 1
libs/expat/Makefile.in

@@ -22,7 +22,7 @@
 #                      \___/_/\_\ .__/ \__,_|\__|
 #                      \___/_/\_\ .__/ \__,_|\__|
 #                               |_| XML parser
 #                               |_| XML parser
 #
 #
-# Copyright (c) 2017-2023 Sebastian Pipping <[email protected]>
+# Copyright (c) 2017-2025 Sebastian Pipping <[email protected]>
 # Copyright (c) 2018      KangLin <[email protected]>
 # Copyright (c) 2018      KangLin <[email protected]>
 # Copyright (c) 2022      Johnny Jazeix <[email protected]>
 # Copyright (c) 2022      Johnny Jazeix <[email protected]>
 # Copyright (c) 2023      Sony Corporation / Snild Dolkow <[email protected]>
 # Copyright (c) 2023      Sony Corporation / Snild Dolkow <[email protected]>
@@ -494,6 +494,8 @@ EXTRA_DIST = \
     conftools/expat.m4 \
     conftools/expat.m4 \
     conftools/get-version.sh \
     conftools/get-version.sh \
     \
     \
+    fuzz/xml_lpm_fuzzer.cpp \
+    fuzz/xml_lpm_fuzzer.proto \
     fuzz/xml_parsebuffer_fuzzer.c \
     fuzz/xml_parsebuffer_fuzzer.c \
     fuzz/xml_parse_fuzzer.c \
     fuzz/xml_parse_fuzzer.c \
     \
     \

+ 2 - 1
libs/expat/README.md

@@ -3,6 +3,7 @@
 [![Packaging status](https://repology.org/badge/tiny-repos/expat.svg)](https://repology.org/metapackage/expat/versions)
 [![Packaging status](https://repology.org/badge/tiny-repos/expat.svg)](https://repology.org/metapackage/expat/versions)
 [![Downloads SourceForge](https://img.shields.io/sourceforge/dt/expat?label=Downloads%20SourceForge)](https://sourceforge.net/projects/expat/files/)
 [![Downloads SourceForge](https://img.shields.io/sourceforge/dt/expat?label=Downloads%20SourceForge)](https://sourceforge.net/projects/expat/files/)
 [![Downloads GitHub](https://img.shields.io/github/downloads/libexpat/libexpat/total?label=Downloads%20GitHub)](https://github.com/libexpat/libexpat/releases)
 [![Downloads GitHub](https://img.shields.io/github/downloads/libexpat/libexpat/total?label=Downloads%20GitHub)](https://github.com/libexpat/libexpat/releases)
+[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10205/badge)](https://www.bestpractices.dev/projects/10205)
 
 
 > [!CAUTION]
 > [!CAUTION]
 >
 >
@@ -11,7 +12,7 @@
 > at the top of the `Changes` file.
 > at the top of the `Changes` file.
 
 
 
 
-# Expat, Release 2.7.0
+# Expat, Release 2.7.1
 
 
 This is Expat, a C99 library for parsing
 This is Expat, a C99 library for parsing
 [XML 1.0 Fourth Edition](https://www.w3.org/TR/2006/REC-xml-20060816/), started by
 [XML 1.0 Fourth Edition](https://www.w3.org/TR/2006/REC-xml-20060816/), started by

+ 11 - 11
libs/expat/configure

@@ -1,6 +1,6 @@
 #! /bin/sh
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for expat 2.7.0.
+# Generated by GNU Autoconf 2.71 for expat 2.7.1.
 #
 #
 # Report bugs to <https://github.com/libexpat/libexpat/issues>.
 # Report bugs to <https://github.com/libexpat/libexpat/issues>.
 #
 #
@@ -621,8 +621,8 @@ MAKEFLAGS=
 # Identity of this package.
 # Identity of this package.
 PACKAGE_NAME='expat'
 PACKAGE_NAME='expat'
 PACKAGE_TARNAME='expat'
 PACKAGE_TARNAME='expat'
-PACKAGE_VERSION='2.7.0'
-PACKAGE_STRING='expat 2.7.0'
+PACKAGE_VERSION='2.7.1'
+PACKAGE_STRING='expat 2.7.1'
 PACKAGE_BUGREPORT='https://github.com/libexpat/libexpat/issues'
 PACKAGE_BUGREPORT='https://github.com/libexpat/libexpat/issues'
 PACKAGE_URL=''
 PACKAGE_URL=''
 
 
@@ -1426,7 +1426,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
   cat <<_ACEOF
-\`configure' configures expat 2.7.0 to adapt to many kinds of systems.
+\`configure' configures expat 2.7.1 to adapt to many kinds of systems.
 
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
 
@@ -1497,7 +1497,7 @@ fi
 
 
 if test -n "$ac_init_help"; then
 if test -n "$ac_init_help"; then
   case $ac_init_help in
   case $ac_init_help in
-     short | recursive ) echo "Configuration of expat 2.7.0:";;
+     short | recursive ) echo "Configuration of expat 2.7.1:";;
    esac
    esac
   cat <<\_ACEOF
   cat <<\_ACEOF
 
 
@@ -1634,7 +1634,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
 if $ac_init_version; then
   cat <<\_ACEOF
   cat <<\_ACEOF
-expat configure 2.7.0
+expat configure 2.7.1
 generated by GNU Autoconf 2.71
 generated by GNU Autoconf 2.71
 
 
 Copyright (C) 2021 Free Software Foundation, Inc.
 Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2265,7 +2265,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 running configure, to aid debugging if configure makes a mistake.
 
 
-It was created by expat $as_me 2.7.0, which was
+It was created by expat $as_me 2.7.1, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
 
   $ $0$ac_configure_args_raw
   $ $0$ac_configure_args_raw
@@ -3831,7 +3831,7 @@ fi
 
 
 # Define the identity of the package.
 # Define the identity of the package.
  PACKAGE='expat'
  PACKAGE='expat'
- VERSION='2.7.0'
+ VERSION='2.7.1'
 
 
 
 
 printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
 printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -3962,7 +3962,7 @@ fi
 
 
 
 
 LIBCURRENT=11  # sync
 LIBCURRENT=11  # sync
-LIBREVISION=1  # with
+LIBREVISION=2  # with
 LIBAGE=10      # CMakeLists.txt!
 LIBAGE=10      # CMakeLists.txt!
 
 
 ac_config_headers="$ac_config_headers expat_config.h"
 ac_config_headers="$ac_config_headers expat_config.h"
@@ -22099,7 +22099,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 # values after options handling.
 ac_log="
 ac_log="
-This file was extended by expat $as_me 2.7.0, which was
+This file was extended by expat $as_me 2.7.1, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
 
   CONFIG_FILES    = $CONFIG_FILES
   CONFIG_FILES    = $CONFIG_FILES
@@ -22167,7 +22167,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
 ac_cs_version="\\
-expat config.status 2.7.0
+expat config.status 2.7.1
 configured by $0, generated by GNU Autoconf 2.71,
 configured by $0, generated by GNU Autoconf 2.71,
   with options \\"\$ac_cs_config\\"
   with options \\"\$ac_cs_config\\"
 
 

+ 1 - 1
libs/expat/configure.ac

@@ -85,7 +85,7 @@ dnl If the API changes incompatibly set LIBAGE back to 0
 dnl
 dnl
 
 
 LIBCURRENT=11  # sync
 LIBCURRENT=11  # sync
-LIBREVISION=1  # with
+LIBREVISION=2  # with
 LIBAGE=10      # CMakeLists.txt!
 LIBAGE=10      # CMakeLists.txt!
 
 
 AC_CONFIG_HEADERS([expat_config.h])
 AC_CONFIG_HEADERS([expat_config.h])

+ 1 - 1
libs/expat/doc/reference.html

@@ -52,7 +52,7 @@
   <div>
   <div>
     <h1>
     <h1>
       The Expat XML Parser
       The Expat XML Parser
-      <small>Release 2.7.0</small>
+      <small>Release 2.7.1</small>
     </h1>
     </h1>
   </div>
   </div>
 <div class="content">
 <div class="content">

+ 1 - 1
libs/expat/doc/xmlwf.1

@@ -5,7 +5,7 @@
 \\$2 \(la\\$1\(ra\\$3
 \\$2 \(la\\$1\(ra\\$3
 ..
 ..
 .if \n(.g .mso www.tmac
 .if \n(.g .mso www.tmac
-.TH XMLWF 1 "March 13, 2025" "" ""
+.TH XMLWF 1 "March 27, 2025" "" ""
 .SH NAME
 .SH NAME
 xmlwf \- Determines if an XML document is well-formed
 xmlwf \- Determines if an XML document is well-formed
 .SH SYNOPSIS
 .SH SYNOPSIS

+ 1 - 1
libs/expat/doc/xmlwf.xml

@@ -21,7 +21,7 @@
           "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
           "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
   <!ENTITY dhfirstname "<firstname>Scott</firstname>">
   <!ENTITY dhfirstname "<firstname>Scott</firstname>">
   <!ENTITY dhsurname   "<surname>Bronson</surname>">
   <!ENTITY dhsurname   "<surname>Bronson</surname>">
-  <!ENTITY dhdate      "<date>March 13, 2025</date>">
+  <!ENTITY dhdate      "<date>March 27, 2025</date>">
   <!-- Please adjust this^^ date whenever cutting a new release. -->
   <!-- Please adjust this^^ date whenever cutting a new release. -->
   <!ENTITY dhsection   "<manvolnum>1</manvolnum>">
   <!ENTITY dhsection   "<manvolnum>1</manvolnum>">
   <!ENTITY dhemail     "<email>[email protected]</email>">
   <!ENTITY dhemail     "<email>[email protected]</email>">

+ 3 - 3
libs/expat/expat_config.h

@@ -83,7 +83,7 @@
 #define PACKAGE_NAME "expat"
 #define PACKAGE_NAME "expat"
 
 
 /* Define to the full name and version of this package. */
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "expat 2.7.0"
+#define PACKAGE_STRING "expat 2.7.1"
 
 
 /* Define to the one symbol short name of this package. */
 /* Define to the one symbol short name of this package. */
 #define PACKAGE_TARNAME "expat"
 #define PACKAGE_TARNAME "expat"
@@ -92,7 +92,7 @@
 #define PACKAGE_URL ""
 #define PACKAGE_URL ""
 
 
 /* Define to the version of this package. */
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "2.7.0"
+#define PACKAGE_VERSION "2.7.1"
 
 
 /* Define to 1 if all of the C90 standard headers exist (not just the ones
 /* Define to 1 if all of the C90 standard headers exist (not just the ones
    required in a freestanding environment). This macro is provided for
    required in a freestanding environment). This macro is provided for
@@ -100,7 +100,7 @@
 #define STDC_HEADERS 1
 #define STDC_HEADERS 1
 
 
 /* Version number of package */
 /* Version number of package */
-#define VERSION "2.7.0"
+#define VERSION "2.7.1"
 
 
 /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
 /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
    significant byte first (like Motorola and SPARC, unlike Intel). */
    significant byte first (like Motorola and SPARC, unlike Intel). */

+ 464 - 0
libs/expat/fuzz/xml_lpm_fuzzer.cpp

@@ -0,0 +1,464 @@
+/*
+                            __  __            _
+                         ___\ \/ /_ __   __ _| |_
+                        / _ \\  /| '_ \ / _` | __|
+                       |  __//  \| |_) | (_| | |_
+                        \___/_/\_\ .__/ \__,_|\__|
+                                 |_| XML parser
+
+   Copyright (c) 2022 Mark Brand <[email protected]>
+   Copyright (c) 2025 Sebastian Pipping <[email protected]>
+   Licensed under the MIT license:
+
+   Permission is  hereby granted,  free of charge,  to any  person obtaining
+   a  copy  of  this  software   and  associated  documentation  files  (the
+   "Software"),  to  deal in  the  Software  without restriction,  including
+   without  limitation the  rights  to use,  copy,  modify, merge,  publish,
+   distribute, sublicense, and/or sell copies of the Software, and to permit
+   persons  to whom  the Software  is  furnished to  do so,  subject to  the
+   following conditions:
+
+   The above copyright  notice and this permission notice  shall be included
+   in all copies or substantial portions of the Software.
+
+   THE  SOFTWARE  IS  PROVIDED  "AS  IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
+   EXPRESS  OR IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO  THE WARRANTIES  OF
+   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+   NO EVENT SHALL THE AUTHORS OR  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+   DAMAGES OR  OTHER LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT,  TORT OR
+   OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+   USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#if defined(NDEBUG)
+#  undef NDEBUG // because checks below rely on assert(...)
+#endif
+
+#include <assert.h>
+#include <stdint.h>
+#include <vector>
+
+#include "expat.h"
+#include "xml_lpm_fuzzer.pb.h"
+#include "src/libfuzzer/libfuzzer_macro.h"
+
+static const char *g_encoding = nullptr;
+static const char *g_external_entity = nullptr;
+static size_t g_external_entity_size = 0;
+
+void
+SetEncoding(const xml_lpm_fuzzer::Encoding &e) {
+  switch (e) {
+  case xml_lpm_fuzzer::Encoding::UTF8:
+    g_encoding = "UTF-8";
+    break;
+
+  case xml_lpm_fuzzer::Encoding::UTF16:
+    g_encoding = "UTF-16";
+    break;
+
+  case xml_lpm_fuzzer::Encoding::ISO88591:
+    g_encoding = "ISO-8859-1";
+    break;
+
+  case xml_lpm_fuzzer::Encoding::ASCII:
+    g_encoding = "US-ASCII";
+    break;
+
+  case xml_lpm_fuzzer::Encoding::NONE:
+    g_encoding = NULL;
+    break;
+
+  default:
+    g_encoding = "UNKNOWN";
+    break;
+  }
+}
+
+static int g_allocation_count = 0;
+static std::vector<int> g_fail_allocations = {};
+
+void *
+MallocHook(size_t size) {
+  g_allocation_count += 1;
+  for (auto index : g_fail_allocations) {
+    if (index == g_allocation_count) {
+      return NULL;
+    }
+  }
+  return malloc(size);
+}
+
+void *
+ReallocHook(void *ptr, size_t size) {
+  g_allocation_count += 1;
+  for (auto index : g_fail_allocations) {
+    if (index == g_allocation_count) {
+      return NULL;
+    }
+  }
+  return realloc(ptr, size);
+}
+
+void
+FreeHook(void *ptr) {
+  free(ptr);
+}
+
+XML_Memory_Handling_Suite memory_handling_suite
+    = {MallocHook, ReallocHook, FreeHook};
+
+void InitializeParser(XML_Parser parser);
+
+// We want a parse function that supports resumption, so that we can cover the
+// suspend/resume code.
+enum XML_Status
+Parse(XML_Parser parser, const char *input, int input_len, int is_final) {
+  enum XML_Status status = XML_Parse(parser, input, input_len, is_final);
+  while (status == XML_STATUS_SUSPENDED) {
+    status = XML_ResumeParser(parser);
+  }
+  return status;
+}
+
+// When the fuzzer is compiled with instrumentation such as ASan, then the
+// accesses in TouchString will fault if they access invalid memory (ie. detect
+// either a use-after-free or buffer-overflow). By calling TouchString in each
+// of the callbacks, we can check that the arguments meet the API specifications
+// in terms of length/null-termination. no_optimize is used to ensure that the
+// compiler has to emit actual memory reads, instead of removing them.
+static volatile size_t no_optimize = 0;
+static void
+TouchString(const XML_Char *ptr, int len = -1) {
+  if (! ptr) {
+    return;
+  }
+
+  if (len == -1) {
+    for (XML_Char value = *ptr++; value; value = *ptr++) {
+      no_optimize += value;
+    }
+  } else {
+    for (int i = 0; i < len; ++i) {
+      no_optimize += ptr[i];
+    }
+  }
+}
+
+static void
+TouchNodeAndRecurse(XML_Content *content) {
+  switch (content->type) {
+  case XML_CTYPE_EMPTY:
+  case XML_CTYPE_ANY:
+    assert(content->quant == XML_CQUANT_NONE);
+    assert(content->name == NULL);
+    assert(content->numchildren == 0);
+    assert(content->children == NULL);
+    break;
+
+  case XML_CTYPE_MIXED:
+    assert(content->quant == XML_CQUANT_NONE
+           || content->quant == XML_CQUANT_REP);
+    assert(content->name == NULL);
+    for (unsigned int i = 0; i < content->numchildren; ++i) {
+      assert(content->children[i].type == XML_CTYPE_NAME);
+      assert(content->children[i].quant == XML_CQUANT_NONE);
+      assert(content->children[i].numchildren == 0);
+      assert(content->children[i].children == NULL);
+      TouchString(content->children[i].name);
+    }
+    break;
+
+  case XML_CTYPE_NAME:
+    assert((content->quant == XML_CQUANT_NONE)
+           || (content->quant == XML_CQUANT_OPT)
+           || (content->quant == XML_CQUANT_REP)
+           || (content->quant == XML_CQUANT_PLUS));
+    assert(content->numchildren == 0);
+    assert(content->children == NULL);
+    TouchString(content->name);
+    break;
+
+  case XML_CTYPE_CHOICE:
+  case XML_CTYPE_SEQ:
+    assert((content->quant == XML_CQUANT_NONE)
+           || (content->quant == XML_CQUANT_OPT)
+           || (content->quant == XML_CQUANT_REP)
+           || (content->quant == XML_CQUANT_PLUS));
+    assert(content->name == NULL);
+    for (unsigned int i = 0; i < content->numchildren; ++i) {
+      TouchNodeAndRecurse(&content->children[i]);
+    }
+    break;
+
+  default:
+    assert(false);
+  }
+}
+
+static void XMLCALL
+ElementDeclHandler(void *userData, const XML_Char *name, XML_Content *model) {
+  TouchString(name);
+  TouchNodeAndRecurse(model);
+  XML_FreeContentModel((XML_Parser)userData, model);
+}
+
+static void XMLCALL
+AttlistDeclHandler(void *userData, const XML_Char *elname,
+                   const XML_Char *attname, const XML_Char *atttype,
+                   const XML_Char *dflt, int isrequired) {
+  (void)userData;
+  TouchString(elname);
+  TouchString(attname);
+  TouchString(atttype);
+  TouchString(dflt);
+  (void)isrequired;
+}
+
+static void XMLCALL
+XmlDeclHandler(void *userData, const XML_Char *version,
+               const XML_Char *encoding, int standalone) {
+  (void)userData;
+  TouchString(version);
+  TouchString(encoding);
+  (void)standalone;
+}
+
+static void XMLCALL
+StartElementHandler(void *userData, const XML_Char *name,
+                    const XML_Char **atts) {
+  (void)userData;
+  TouchString(name);
+  for (size_t i = 0; atts[i] != NULL; ++i) {
+    TouchString(atts[i]);
+  }
+}
+
+static void XMLCALL
+EndElementHandler(void *userData, const XML_Char *name) {
+  (void)userData;
+  TouchString(name);
+}
+
+static void XMLCALL
+CharacterDataHandler(void *userData, const XML_Char *s, int len) {
+  (void)userData;
+  TouchString(s, len);
+}
+
+static void XMLCALL
+ProcessingInstructionHandler(void *userData, const XML_Char *target,
+                             const XML_Char *data) {
+  (void)userData;
+  TouchString(target);
+  TouchString(data);
+}
+
+static void XMLCALL
+CommentHandler(void *userData, const XML_Char *data) {
+  TouchString(data);
+  // Use the comment handler to trigger parser suspend, so that we can get
+  // coverage of that code.
+  XML_StopParser((XML_Parser)userData, XML_TRUE);
+}
+
+static void XMLCALL
+StartCdataSectionHandler(void *userData) {
+  (void)userData;
+}
+
+static void XMLCALL
+EndCdataSectionHandler(void *userData) {
+  (void)userData;
+}
+
+static void XMLCALL
+DefaultHandler(void *userData, const XML_Char *s, int len) {
+  (void)userData;
+  TouchString(s, len);
+}
+
+static void XMLCALL
+StartDoctypeDeclHandler(void *userData, const XML_Char *doctypeName,
+                        const XML_Char *sysid, const XML_Char *pubid,
+                        int has_internal_subset) {
+  (void)userData;
+  TouchString(doctypeName);
+  TouchString(sysid);
+  TouchString(pubid);
+  (void)has_internal_subset;
+}
+
+static void XMLCALL
+EndDoctypeDeclHandler(void *userData) {
+  (void)userData;
+}
+
+static void XMLCALL
+EntityDeclHandler(void *userData, const XML_Char *entityName,
+                  int is_parameter_entity, const XML_Char *value,
+                  int value_length, const XML_Char *base,
+                  const XML_Char *systemId, const XML_Char *publicId,
+                  const XML_Char *notationName) {
+  (void)userData;
+  TouchString(entityName);
+  (void)is_parameter_entity;
+  TouchString(value, value_length);
+  TouchString(base);
+  TouchString(systemId);
+  TouchString(publicId);
+  TouchString(notationName);
+}
+
+static void XMLCALL
+NotationDeclHandler(void *userData, const XML_Char *notationName,
+                    const XML_Char *base, const XML_Char *systemId,
+                    const XML_Char *publicId) {
+  (void)userData;
+  TouchString(notationName);
+  TouchString(base);
+  TouchString(systemId);
+  TouchString(publicId);
+}
+
+static void XMLCALL
+StartNamespaceDeclHandler(void *userData, const XML_Char *prefix,
+                          const XML_Char *uri) {
+  (void)userData;
+  TouchString(prefix);
+  TouchString(uri);
+}
+
+static void XMLCALL
+EndNamespaceDeclHandler(void *userData, const XML_Char *prefix) {
+  (void)userData;
+  TouchString(prefix);
+}
+
+static int XMLCALL
+NotStandaloneHandler(void *userData) {
+  (void)userData;
+  return XML_STATUS_OK;
+}
+
+static int XMLCALL
+ExternalEntityRefHandler(XML_Parser parser, const XML_Char *context,
+                         const XML_Char *base, const XML_Char *systemId,
+                         const XML_Char *publicId) {
+  int rc = XML_STATUS_ERROR;
+  TouchString(context);
+  TouchString(base);
+  TouchString(systemId);
+  TouchString(publicId);
+
+  if (g_external_entity) {
+    XML_Parser ext_parser
+        = XML_ExternalEntityParserCreate(parser, context, g_encoding);
+    rc = Parse(ext_parser, g_external_entity, g_external_entity_size, 1);
+    XML_ParserFree(ext_parser);
+  }
+
+  return rc;
+}
+
+static void XMLCALL
+SkippedEntityHandler(void *userData, const XML_Char *entityName,
+                     int is_parameter_entity) {
+  (void)userData;
+  TouchString(entityName);
+  (void)is_parameter_entity;
+}
+
+static int XMLCALL
+UnknownEncodingHandler(void *encodingHandlerData, const XML_Char *name,
+                       XML_Encoding *info) {
+  (void)encodingHandlerData;
+  TouchString(name);
+  (void)info;
+  return XML_STATUS_ERROR;
+}
+
+void
+InitializeParser(XML_Parser parser) {
+  XML_SetUserData(parser, (void *)parser);
+  XML_SetHashSalt(parser, 0x41414141);
+  XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
+
+  XML_SetElementDeclHandler(parser, ElementDeclHandler);
+  XML_SetAttlistDeclHandler(parser, AttlistDeclHandler);
+  XML_SetXmlDeclHandler(parser, XmlDeclHandler);
+  XML_SetElementHandler(parser, StartElementHandler, EndElementHandler);
+  XML_SetCharacterDataHandler(parser, CharacterDataHandler);
+  XML_SetProcessingInstructionHandler(parser, ProcessingInstructionHandler);
+  XML_SetCommentHandler(parser, CommentHandler);
+  XML_SetCdataSectionHandler(parser, StartCdataSectionHandler,
+                             EndCdataSectionHandler);
+  // XML_SetDefaultHandler disables entity expansion
+  XML_SetDefaultHandlerExpand(parser, DefaultHandler);
+  XML_SetDoctypeDeclHandler(parser, StartDoctypeDeclHandler,
+                            EndDoctypeDeclHandler);
+  // Note: This is mutually exclusive with XML_SetUnparsedEntityDeclHandler,
+  //       and there isn't any significant code change between the two.
+  XML_SetEntityDeclHandler(parser, EntityDeclHandler);
+  XML_SetNotationDeclHandler(parser, NotationDeclHandler);
+  XML_SetNamespaceDeclHandler(parser, StartNamespaceDeclHandler,
+                              EndNamespaceDeclHandler);
+  XML_SetNotStandaloneHandler(parser, NotStandaloneHandler);
+  XML_SetExternalEntityRefHandler(parser, ExternalEntityRefHandler);
+  XML_SetSkippedEntityHandler(parser, SkippedEntityHandler);
+  XML_SetUnknownEncodingHandler(parser, UnknownEncodingHandler, (void *)parser);
+}
+
+DEFINE_TEXT_PROTO_FUZZER(const xml_lpm_fuzzer::Testcase &testcase) {
+  g_external_entity = nullptr;
+
+  if (! testcase.actions_size()) {
+    return;
+  }
+
+  g_allocation_count = 0;
+  g_fail_allocations.clear();
+  for (int i = 0; i < testcase.fail_allocations_size(); ++i) {
+    g_fail_allocations.push_back(testcase.fail_allocations(i));
+  }
+
+  SetEncoding(testcase.encoding());
+  XML_Parser parser
+      = XML_ParserCreate_MM(g_encoding, &memory_handling_suite, "|");
+  InitializeParser(parser);
+
+  for (int i = 0; i < testcase.actions_size(); ++i) {
+    const auto &action = testcase.actions(i);
+    switch (action.action_case()) {
+    case xml_lpm_fuzzer::Action::kChunk:
+      if (XML_STATUS_ERROR
+          == Parse(parser, action.chunk().data(), action.chunk().size(), 0)) {
+        // Force a reset after parse error.
+        XML_ParserReset(parser, g_encoding);
+        InitializeParser(parser);
+      }
+      break;
+
+    case xml_lpm_fuzzer::Action::kLastChunk:
+      Parse(parser, action.last_chunk().data(), action.last_chunk().size(), 1);
+      XML_ParserReset(parser, g_encoding);
+      InitializeParser(parser);
+      break;
+
+    case xml_lpm_fuzzer::Action::kReset:
+      XML_ParserReset(parser, g_encoding);
+      InitializeParser(parser);
+      break;
+
+    case xml_lpm_fuzzer::Action::kExternalEntity:
+      g_external_entity = action.external_entity().data();
+      g_external_entity_size = action.external_entity().size();
+      break;
+
+    default:
+      break;
+    }
+  }
+
+  XML_ParserFree(parser);
+}

+ 58 - 0
libs/expat/fuzz/xml_lpm_fuzzer.proto

@@ -0,0 +1,58 @@
+/*
+                            __  __            _
+                         ___\ \/ /_ __   __ _| |_
+                        / _ \\  /| '_ \ / _` | __|
+                       |  __//  \| |_) | (_| | |_
+                        \___/_/\_\ .__/ \__,_|\__|
+                                 |_| XML parser
+
+   Copyright (c) 2022 Mark Brand <[email protected]>
+   Copyright (c) 2025 Sebastian Pipping <[email protected]>
+   Licensed under the MIT license:
+
+   Permission is  hereby granted,  free of charge,  to any  person obtaining
+   a  copy  of  this  software   and  associated  documentation  files  (the
+   "Software"),  to  deal in  the  Software  without restriction,  including
+   without  limitation the  rights  to use,  copy,  modify, merge,  publish,
+   distribute, sublicense, and/or sell copies of the Software, and to permit
+   persons  to whom  the Software  is  furnished to  do so,  subject to  the
+   following conditions:
+
+   The above copyright  notice and this permission notice  shall be included
+   in all copies or substantial portions of the Software.
+
+   THE  SOFTWARE  IS  PROVIDED  "AS  IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
+   EXPRESS  OR IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO  THE WARRANTIES  OF
+   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+   NO EVENT SHALL THE AUTHORS OR  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+   DAMAGES OR  OTHER LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT,  TORT OR
+   OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+   USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+syntax = "proto2";
+package xml_lpm_fuzzer;
+
+enum Encoding {
+  UTF8 = 0;
+  UTF16 = 1;
+  ISO88591 = 2;
+  ASCII = 3;
+  UNKNOWN = 4;
+  NONE = 5;
+}
+
+message Action {
+  oneof action {
+    string chunk = 1;
+    string last_chunk = 2;
+    bool reset = 3;
+    string external_entity = 4;
+  }
+}
+
+message Testcase {
+  required Encoding encoding = 1;
+  repeated Action actions = 2;
+  repeated int32 fail_allocations = 3;
+}

+ 1 - 1
libs/expat/lib/expat.h

@@ -1068,7 +1068,7 @@ XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled);
 */
 */
 #define XML_MAJOR_VERSION 2
 #define XML_MAJOR_VERSION 2
 #define XML_MINOR_VERSION 7
 #define XML_MINOR_VERSION 7
-#define XML_MICRO_VERSION 0
+#define XML_MICRO_VERSION 1
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 4 - 1
libs/expat/lib/internal.h

@@ -28,7 +28,7 @@
    Copyright (c) 2002-2003 Fred L. Drake, Jr. <[email protected]>
    Copyright (c) 2002-2003 Fred L. Drake, Jr. <[email protected]>
    Copyright (c) 2002-2006 Karl Waclawek <[email protected]>
    Copyright (c) 2002-2006 Karl Waclawek <[email protected]>
    Copyright (c) 2003      Greg Stein <[email protected]>
    Copyright (c) 2003      Greg Stein <[email protected]>
-   Copyright (c) 2016-2024 Sebastian Pipping <[email protected]>
+   Copyright (c) 2016-2025 Sebastian Pipping <[email protected]>
    Copyright (c) 2018      Yury Gribov <[email protected]>
    Copyright (c) 2018      Yury Gribov <[email protected]>
    Copyright (c) 2019      David Loffredo <[email protected]>
    Copyright (c) 2019      David Loffredo <[email protected]>
    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <[email protected]>
    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <[email protected]>
@@ -127,6 +127,9 @@
 #  elif ULONG_MAX == 18446744073709551615u // 2^64-1
 #  elif ULONG_MAX == 18446744073709551615u // 2^64-1
 #    define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld"
 #    define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld"
 #    define EXPAT_FMT_SIZE_T(midpart) "%" midpart "lu"
 #    define EXPAT_FMT_SIZE_T(midpart) "%" midpart "lu"
+#  elif defined(EMSCRIPTEN) // 32bit mode Emscripten
+#    define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld"
+#    define EXPAT_FMT_SIZE_T(midpart) "%" midpart "zu"
 #  else
 #  else
 #    define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d"
 #    define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d"
 #    define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"
 #    define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"

+ 11 - 5
libs/expat/lib/xmlparse.c

@@ -1,4 +1,4 @@
-/* 7d6840a33c250b74adb0ba295d6ec818dccebebaffc8c3ed27d0b29c28adbeb3 (2.7.0+)
+/* d19ae032c224863c1527ba44d228cc34b99192c3a4c5a27af1f4e054d45ee031 (2.7.1+)
                             __  __            _
                             __  __            _
                          ___\ \/ /_ __   __ _| |_
                          ___\ \/ /_ __   __ _| |_
                         / _ \\  /| '_ \ / _` | __|
                         / _ \\  /| '_ \ / _` | __|
@@ -3402,12 +3402,13 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
       break;
       break;
       /* LCOV_EXCL_STOP */
       /* LCOV_EXCL_STOP */
     }
     }
-    *eventPP = s = next;
     switch (parser->m_parsingStatus.parsing) {
     switch (parser->m_parsingStatus.parsing) {
     case XML_SUSPENDED:
     case XML_SUSPENDED:
+      *eventPP = next;
       *nextPtr = next;
       *nextPtr = next;
       return XML_ERROR_NONE;
       return XML_ERROR_NONE;
     case XML_FINISHED:
     case XML_FINISHED:
+      *eventPP = next;
       return XML_ERROR_ABORTED;
       return XML_ERROR_ABORTED;
     case XML_PARSING:
     case XML_PARSING:
       if (parser->m_reenter) {
       if (parser->m_reenter) {
@@ -3416,6 +3417,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
       }
       }
       /* Fall through */
       /* Fall through */
     default:;
     default:;
+      *eventPP = s = next;
     }
     }
   }
   }
   /* not reached */
   /* not reached */
@@ -4332,12 +4334,13 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
       /* LCOV_EXCL_STOP */
       /* LCOV_EXCL_STOP */
     }
     }
 
 
-    *eventPP = s = next;
     switch (parser->m_parsingStatus.parsing) {
     switch (parser->m_parsingStatus.parsing) {
     case XML_SUSPENDED:
     case XML_SUSPENDED:
+      *eventPP = next;
       *nextPtr = next;
       *nextPtr = next;
       return XML_ERROR_NONE;
       return XML_ERROR_NONE;
     case XML_FINISHED:
     case XML_FINISHED:
+      *eventPP = next;
       return XML_ERROR_ABORTED;
       return XML_ERROR_ABORTED;
     case XML_PARSING:
     case XML_PARSING:
       if (parser->m_reenter) {
       if (parser->m_reenter) {
@@ -4345,6 +4348,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
       }
       }
       /* Fall through */
       /* Fall through */
     default:;
     default:;
+      *eventPP = s = next;
     }
     }
   }
   }
   /* not reached */
   /* not reached */
@@ -5951,12 +5955,13 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
     default:
     default:
       return XML_ERROR_JUNK_AFTER_DOC_ELEMENT;
       return XML_ERROR_JUNK_AFTER_DOC_ELEMENT;
     }
     }
-    parser->m_eventPtr = s = next;
     switch (parser->m_parsingStatus.parsing) {
     switch (parser->m_parsingStatus.parsing) {
     case XML_SUSPENDED:
     case XML_SUSPENDED:
+      parser->m_eventPtr = next;
       *nextPtr = next;
       *nextPtr = next;
       return XML_ERROR_NONE;
       return XML_ERROR_NONE;
     case XML_FINISHED:
     case XML_FINISHED:
+      parser->m_eventPtr = next;
       return XML_ERROR_ABORTED;
       return XML_ERROR_ABORTED;
     case XML_PARSING:
     case XML_PARSING:
       if (parser->m_reenter) {
       if (parser->m_reenter) {
@@ -5964,6 +5969,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
       }
       }
     /* Fall through */
     /* Fall through */
     default:;
     default:;
+      parser->m_eventPtr = s = next;
     }
     }
   }
   }
 }
 }
@@ -8245,7 +8251,7 @@ entityTrackingReportStats(XML_Parser rootParser, ENTITY *entity,
       (void *)rootParser, rootParser->m_entity_stats.countEverOpened,
       (void *)rootParser, rootParser->m_entity_stats.countEverOpened,
       rootParser->m_entity_stats.currentDepth,
       rootParser->m_entity_stats.currentDepth,
       rootParser->m_entity_stats.maximumDepthSeen,
       rootParser->m_entity_stats.maximumDepthSeen,
-      (rootParser->m_entity_stats.currentDepth - 1) * 2, "",
+      ((int)rootParser->m_entity_stats.currentDepth - 1) * 2, "",
       entity->is_param ? "%" : "&", entityName, action, entity->textLen,
       entity->is_param ? "%" : "&", entityName, action, entity->textLen,
       sourceLine);
       sourceLine);
 }
 }

+ 3 - 6
libs/expat/tests/benchmark/benchmark.c

@@ -114,22 +114,19 @@ main(int argc, char *argv[]) {
   bufferSize = atoi(argv[j + 2]);
   bufferSize = atoi(argv[j + 2]);
   nrOfLoops = atoi(argv[j + 3]);
   nrOfLoops = atoi(argv[j + 3]);
   if (bufferSize <= 0 || nrOfLoops <= 0) {
   if (bufferSize <= 0 || nrOfLoops <= 0) {
-    fclose(file);
-    close(fd);
+    fclose(file); // NOTE: this closes fd as well
     fprintf(stderr, "buffer size and nr of loops must be greater than zero.\n");
     fprintf(stderr, "buffer size and nr of loops must be greater than zero.\n");
     return 3;
     return 3;
   }
   }
 
 
   XMLBuf = malloc(fileAttr.st_size);
   XMLBuf = malloc(fileAttr.st_size);
   if (XMLBuf == NULL) {
   if (XMLBuf == NULL) {
-    fclose(file);
-    close(fd);
+    fclose(file); // NOTE: this closes fd as well
     fprintf(stderr, "ouf of memory.\n");
     fprintf(stderr, "ouf of memory.\n");
     return 5;
     return 5;
   }
   }
   fileSize = fread(XMLBuf, sizeof(char), fileAttr.st_size, file);
   fileSize = fread(XMLBuf, sizeof(char), fileAttr.st_size, file);
-  fclose(file);
-  close(fd);
+  fclose(file); // NOTE: this closes fd as well
 
 
   if (ns)
   if (ns)
     parser = XML_ParserCreateNS(NULL, '!');
     parser = XML_ParserCreateNS(NULL, '!');

+ 26 - 1
libs/expat/tests/common.c

@@ -10,7 +10,7 @@
    Copyright (c) 2003      Greg Stein <[email protected]>
    Copyright (c) 2003      Greg Stein <[email protected]>
    Copyright (c) 2005-2007 Steven Solie <[email protected]>
    Copyright (c) 2005-2007 Steven Solie <[email protected]>
    Copyright (c) 2005-2012 Karl Waclawek <[email protected]>
    Copyright (c) 2005-2012 Karl Waclawek <[email protected]>
-   Copyright (c) 2016-2024 Sebastian Pipping <[email protected]>
+   Copyright (c) 2016-2025 Sebastian Pipping <[email protected]>
    Copyright (c) 2017-2022 Rhodri James <[email protected]>
    Copyright (c) 2017-2022 Rhodri James <[email protected]>
    Copyright (c) 2017      Joe Orton <[email protected]>
    Copyright (c) 2017      Joe Orton <[email protected]>
    Copyright (c) 2017      José Gutiérrez de la Concha <[email protected]>
    Copyright (c) 2017      José Gutiérrez de la Concha <[email protected]>
@@ -42,6 +42,8 @@
 */
 */
 
 
 #include <assert.h>
 #include <assert.h>
+#include <errno.h>
+#include <stdint.h> // for SIZE_MAX
 #include <stdio.h>
 #include <stdio.h>
 #include <string.h>
 #include <string.h>
 
 
@@ -300,3 +302,26 @@ duff_reallocator(void *ptr, size_t size) {
     g_reallocation_count--;
     g_reallocation_count--;
   return realloc(ptr, size);
   return realloc(ptr, size);
 }
 }
+
+// Portable remake of strndup(3) for C99; does not care about space efficiency
+char *
+portable_strndup(const char *s, size_t n) {
+  if ((s == NULL) || (n == SIZE_MAX)) {
+    errno = EINVAL;
+    return NULL;
+  }
+
+  char *const buffer = (char *)malloc(n + 1);
+  if (buffer == NULL) {
+    errno = ENOMEM;
+    return NULL;
+  }
+
+  errno = 0;
+
+  memcpy(buffer, s, n);
+
+  buffer[n] = '\0';
+
+  return buffer;
+}

+ 3 - 1
libs/expat/tests/common.h

@@ -10,7 +10,7 @@
    Copyright (c) 2003      Greg Stein <[email protected]>
    Copyright (c) 2003      Greg Stein <[email protected]>
    Copyright (c) 2005-2007 Steven Solie <[email protected]>
    Copyright (c) 2005-2007 Steven Solie <[email protected]>
    Copyright (c) 2005-2012 Karl Waclawek <[email protected]>
    Copyright (c) 2005-2012 Karl Waclawek <[email protected]>
-   Copyright (c) 2016-2024 Sebastian Pipping <[email protected]>
+   Copyright (c) 2016-2025 Sebastian Pipping <[email protected]>
    Copyright (c) 2017-2022 Rhodri James <[email protected]>
    Copyright (c) 2017-2022 Rhodri James <[email protected]>
    Copyright (c) 2017      Joe Orton <[email protected]>
    Copyright (c) 2017      Joe Orton <[email protected]>
    Copyright (c) 2017      José Gutiérrez de la Concha <[email protected]>
    Copyright (c) 2017      José Gutiérrez de la Concha <[email protected]>
@@ -146,6 +146,8 @@ extern void *duff_allocator(size_t size);
 
 
 extern void *duff_reallocator(void *ptr, size_t size);
 extern void *duff_reallocator(void *ptr, size_t size);
 
 
+extern char *portable_strndup(const char *s, size_t n);
+
 #endif /* XML_COMMON_H */
 #endif /* XML_COMMON_H */
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 62 - 1
libs/expat/tests/misc_tests.c

@@ -211,7 +211,7 @@ START_TEST(test_misc_version) {
   if (! versions_equal(&read_version, &parsed_version))
   if (! versions_equal(&read_version, &parsed_version))
     fail("Version mismatch");
     fail("Version mismatch");
 
 
-  if (xcstrcmp(version_text, XCS("expat_2.7.0"))) /* needs bump on releases */
+  if (xcstrcmp(version_text, XCS("expat_2.7.1"))) /* needs bump on releases */
     fail("XML_*_VERSION in expat.h out of sync?\n");
     fail("XML_*_VERSION in expat.h out of sync?\n");
 }
 }
 END_TEST
 END_TEST
@@ -618,6 +618,66 @@ START_TEST(test_renter_loop_finite_content) {
 }
 }
 END_TEST
 END_TEST
 
 
+// Inspired by function XML_OriginalString of Perl's XML::Parser
+static char *
+dup_original_string(XML_Parser parser) {
+  const int byte_count = XML_GetCurrentByteCount(parser);
+
+  assert_true(byte_count >= 0);
+
+  int offset = -1;
+  int size = -1;
+
+  const char *const context = XML_GetInputContext(parser, &offset, &size);
+
+#if XML_CONTEXT_BYTES > 0
+  assert_true(context != NULL);
+  assert_true(offset >= 0);
+  assert_true(size >= 0);
+  return portable_strndup(context + offset, byte_count);
+#else
+  assert_true(context == NULL);
+  return NULL;
+#endif
+}
+
+static void
+on_characters_issue_980(void *userData, const XML_Char *s, int len) {
+  (void)s;
+  (void)len;
+  XML_Parser parser = (XML_Parser)userData;
+
+  char *const original_string = dup_original_string(parser);
+
+#if XML_CONTEXT_BYTES > 0
+  assert_true(original_string != NULL);
+  assert_true(strcmp(original_string, "&draft.day;") == 0);
+  free(original_string);
+#else
+  assert_true(original_string == NULL);
+#endif
+}
+
+START_TEST(test_misc_expected_event_ptr_issue_980) {
+  // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
+  //       from Perl's XML::Parser
+  const char *const doc = "<!DOCTYPE day [\n"
+                          "  <!ENTITY draft.day '10'>\n"
+                          "]>\n"
+                          "<day>&draft.day;</day>\n";
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  XML_SetUserData(parser, parser);
+  XML_SetCharacterDataHandler(parser, on_characters_issue_980);
+
+  assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
+                                      /*isFinal=*/XML_TRUE)
+              == XML_STATUS_OK);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
 void
 void
 make_miscellaneous_test_case(Suite *s) {
 make_miscellaneous_test_case(Suite *s) {
   TCase *tc_misc = tcase_create("miscellaneous tests");
   TCase *tc_misc = tcase_create("miscellaneous tests");
@@ -645,4 +705,5 @@ make_miscellaneous_test_case(Suite *s) {
   tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
   tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
   tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
   tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
   tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
   tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
+  tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
 }
 }

+ 1 - 1
libs/expat/win32/expat.iss

@@ -38,7 +38,7 @@
 ; OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 ; OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 ; USE OR OTHER DEALINGS IN THE SOFTWARE.
 ; USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 
-#define expatVer "2.7.0"
+#define expatVer "2.7.1"
 
 
 [Setup]
 [Setup]
 AppName=Expat
 AppName=Expat