Author: fanningpj
Date: Wed Jun 24 12:49:22 2026
New Revision: 1935617

Log:
escape ]]> in element content in OptimizedForSpeedSaver. Thanks to aizu-m. This 
closes #71

Modified:
   xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Saver.java
   xmlbeans/trunk/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java

Modified: xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Saver.java
==============================================================================
--- xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Saver.java      
Wed Jun 24 12:13:28 2026        (r1935616)
+++ xmlbeans/trunk/src/main/java/org/apache/xmlbeans/impl/store/Saver.java      
Wed Jun 24 12:49:22 2026        (r1935617)
@@ -2074,10 +2074,11 @@ abstract class Saver {
             int cch = c._cchSrc;
             int off = c._offSrc;
             int index = 0;
+            int trailingBrackets = 0;
             while (index < cch) {
                 int indexLimit = Math.min(index + 512, cch);
                 CharUtil.getChars(_buf, 0, src, off + index, indexLimit - 
index);
-                entitizeAndWriteText(indexLimit - index);
+                trailingBrackets = entitizeAndWriteText(indexLimit - index, 
trailingBrackets);
                 index = indexLimit;
             }
         }
@@ -2113,7 +2114,7 @@ abstract class Saver {
             }
         }
 
-        private void entitizeAndWriteText(int bufLimit) {
+        private int entitizeAndWriteText(int bufLimit, int trailingBrackets) {
             int index = 0;
             for (int i = 0; i < bufLimit; i++) {
                 char c = _buf[i];
@@ -2122,15 +2123,33 @@ abstract class Saver {
                         emit(_buf, index, i - index);
                         emit("&lt;");
                         index = i + 1;
+                        trailingBrackets = 0;
                         break;
                     case '&':
                         emit(_buf, index, i - index);
                         emit("&amp;");
                         index = i + 1;
+                        trailingBrackets = 0;
+                        break;
+                    case '>':
+                        // ']]>' is not allowed in content, so escape the '>' 
that closes it
+                        if (trailingBrackets >= 2) {
+                            emit(_buf, index, i - index);
+                            emit("&gt;");
+                            index = i + 1;
+                        }
+                        trailingBrackets = 0;
+                        break;
+                    case ']':
+                        trailingBrackets++;
+                        break;
+                    default:
+                        trailingBrackets = 0;
                         break;
                 }
             }
             emit(_buf, index, bufLimit - index);
+            return trailingBrackets;
         }
 
         private void entitizeAndWriteCommentText(int bufLimit) {

Modified: 
xmlbeans/trunk/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java
==============================================================================
--- xmlbeans/trunk/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java     
Wed Jun 24 12:13:28 2026        (r1935616)
+++ xmlbeans/trunk/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java     
Wed Jun 24 12:49:22 2026        (r1935617)
@@ -67,6 +67,38 @@ public class SaveOptimizeForSpeedTest {
     }
 
     @Test
+    void testCDataEndInText() throws Exception {
+        XmlObject o = XmlObject.Factory.parse("<root/>");
+        try (XmlCursor cur = o.newCursor()) {
+            cur.toFirstChild();
+            cur.toFirstContentToken();
+            cur.insertChars("a]]>b");
+        }
+        String out = saveForSpeed(o);
+        // ']]>' is forbidden in element content and must be escaped to 
']]&gt;'
+        assertFalse(out.contains("]]>"));
+        // before the fix the literal ']]>' makes this a fatal parse error
+        XmlObject.Factory.parse(out);
+    }
+
+    @Test
+    void testCDataEndAcrossChunkBoundary() throws Exception {
+        // a ']]>' that straddles the 512-char chunk boundary: ']]' end the 
first
+        // chunk, '>' is the first char of the second. The trailing-bracket 
state
+        // has to carry across chunks or the '>' is emitted unescaped.
+        String data = repeat('x', 510) + "]]>" + repeat('y', 600);
+        XmlObject o = XmlObject.Factory.parse("<root/>");
+        try (XmlCursor cur = o.newCursor()) {
+            cur.toFirstChild();
+            cur.toFirstContentToken();
+            cur.insertChars(data);
+        }
+        String out = saveForSpeed(o);
+        assertFalse(out.contains("]]>"));
+        XmlObject.Factory.parse(out);
+    }
+
+    @Test
     void testProcInstTerminatorAcrossChunkBoundary() throws Exception {
         // a '?>' that straddles the 512-char chunk boundary: '?' is the last 
char
         // of the first chunk, '>' the first char of the second. The per-chunk


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to