src/lib/MSPUBParser.cpp |   12 ++++++++++--
 src/lib/MSPUBParser.h   |    2 +-
 2 files changed, 11 insertions(+), 3 deletions(-)

New commits:
commit 3b9ab5b25d0a072009482eac8bc0ba68da952bc4
Author:     Caolán McNamara <[email protected]>
AuthorDate: Fri May 22 14:32:02 2026 +0000
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri May 22 18:02:53 2026 +0200

    cap parseShapeGroup recursion depth
    
    Cap parseShapeGroup at 100 levels of nesting. Real Publisher documents
    nest groups at most a handful deep - LibreOffice core's PowerPoint
    Escher exporter caps emit at 12 with a note that PowerPoint itself
    complains past 16
    
    Change-Id: I58c2fbb4a0c4610943147b7abe6c948413953911
    Reviewed-on: https://gerrit.libreoffice.org/c/libmspub/+/205559
    Tested-by: Caolán McNamara <[email protected]>
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/src/lib/MSPUBParser.cpp b/src/lib/MSPUBParser.cpp
index 214d54c..5c2d173 100644
--- a/src/lib/MSPUBParser.cpp
+++ b/src/lib/MSPUBParser.cpp
@@ -51,6 +51,12 @@ namespace libmspub
 namespace
 {
 
+// Well above the ~12 ceiling LibreOffice's PowerPoint Escher exporter
+// applies (sd/source/filter/eppt/escherex.cxx
+// PptEscherEx::CloseContainer notes PPT itself struggles past 16), well
+// below any stack-overflow risk.
+constexpr unsigned MAX_SHAPE_GROUP_DEPTH = 100;
+
 Underline readUnderline(const unsigned value)
 {
   switch (value & 0xff)
@@ -1579,8 +1585,10 @@ bool 
MSPUBParser::parseEscher(librevenge::RVNGInputStream *input)
   return true;
 }
 
-void MSPUBParser::parseShapeGroup(librevenge::RVNGInputStream *input, const 
EscherContainerInfo &spgr, Coordinate parentCoordinateSystem, Coordinate 
parentGroupAbsoluteCoord)
+void MSPUBParser::parseShapeGroup(librevenge::RVNGInputStream *input, const 
EscherContainerInfo &spgr, Coordinate parentCoordinateSystem, Coordinate 
parentGroupAbsoluteCoord, unsigned depth)
 {
+  if (depth > MAX_SHAPE_GROUP_DEPTH)
+    return;
   EscherContainerInfo shapeOrGroup;
   std::set<unsigned short> types;
   types.insert(OFFICE_ART_SPGR_CONTAINER);
@@ -1591,7 +1599,7 @@ void 
MSPUBParser::parseShapeGroup(librevenge::RVNGInputStream *input, const Esch
     {
     case OFFICE_ART_SPGR_CONTAINER:
       m_collector->beginGroup();
-      parseShapeGroup(input, shapeOrGroup, parentCoordinateSystem, 
parentGroupAbsoluteCoord);
+      parseShapeGroup(input, shapeOrGroup, parentCoordinateSystem, 
parentGroupAbsoluteCoord, depth + 1);
       m_collector->endGroup();
       break;
     case OFFICE_ART_SP_CONTAINER:
diff --git a/src/lib/MSPUBParser.h b/src/lib/MSPUBParser.h
index 9d00a8f..15eb253 100644
--- a/src/lib/MSPUBParser.h
+++ b/src/lib/MSPUBParser.h
@@ -116,7 +116,7 @@ protected:
   void parseColors(librevenge::RVNGInputStream *input, const 
QuillChunkReference &chunk);
   void parseFonts(librevenge::RVNGInputStream *input, const 
QuillChunkReference &chunk);
   void parseDefaultStyle(librevenge::RVNGInputStream *input, const 
QuillChunkReference &chunk);
-  void parseShapeGroup(librevenge::RVNGInputStream *input, const 
EscherContainerInfo &spgr, Coordinate parentCoordinateSystem, Coordinate 
parentGroupAbsoluteCoord);
+  void parseShapeGroup(librevenge::RVNGInputStream *input, const 
EscherContainerInfo &spgr, Coordinate parentCoordinateSystem, Coordinate 
parentGroupAbsoluteCoord, unsigned depth = 0);
   void skipBlock(librevenge::RVNGInputStream *input, MSPUBBlockInfo block);
   void parseEscherShape(librevenge::RVNGInputStream *input, const 
EscherContainerInfo &sp, Coordinate &parentCoordinateSystem, Coordinate 
&parentGroupAbsoluteCoord);
   bool findEscherContainer(librevenge::RVNGInputStream *input, const 
EscherContainerInfo &parent, EscherContainerInfo &out, unsigned short type);

Reply via email to