From 3f4deb91789189521d577457bd6da27de8fd75b1 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 07 Oct 2026 08:31:10 +0000
Subject: [PATCH] [#1153] Parse the whole DN string, and build or split DN strings through DN instead of string operations (#1171)

---
 opendj-server-legacy/src/main/java/org/opends/server/replication/common/MultiDomainServerState.java |   58 +++++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/common/MultiDomainServerState.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/common/MultiDomainServerState.java
index 2500547..a2275b8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/common/MultiDomainServerState.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/common/MultiDomainServerState.java
@@ -13,11 +13,13 @@
  *
  * Copyright 2006-2009 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.replication.common;
 
 import static org.opends.messages.ReplicationMessages.*;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -395,23 +397,18 @@
       try
       {
         // Split the provided multiDomainServerState into domains
-        String[] domains = multiDomainServerState.split(";");
-        for (String domain : domains)
+        for (String domain : splitDomains(multiDomainServerState))
         {
           // For each domain, split the CSNs by server
           // and build a server state (SHOULD BE OPTIMIZED)
           final ServerState serverStateByDomain = new ServerState();
 
-          final String[] fields = domain.split(":");
-          if (fields.length == 0)
+          // The base DN may contain ':' (o=urn:x), but a CSN never does: the state starts after the last one.
+          final int colonPos = domain.lastIndexOf(':');
+          final String domainBaseDN = colonPos >= 0 ? domain.substring(0, colonPos) : domain;
+          if (colonPos >= 0 && colonPos + 1 < domain.length())
           {
-            throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
-                ERR_INVALID_COOKIE_SYNTAX.get(multiDomainServerState));
-          }
-          final String domainBaseDN = fields[0];
-          if (fields.length > 1)
-          {
-            final String serverStateStr = fields[1];
+            final String serverStateStr = domain.substring(colonPos + 1);
             for (String csnStr : serverStateStr.split(" "))
             {
               final CSN csn = new CSN(csnStr);
@@ -421,10 +418,6 @@
           startStates.put(DN.valueOf(domainBaseDN), serverStateByDomain);
         }
       }
-      catch (DirectoryException de)
-      {
-        throw de;
-      }
       catch (Exception e)
       {
         throw new DirectoryException(
@@ -435,4 +428,39 @@
     }
     return startStates;
   }
+
+  /**
+   * Splits the provided cookie at each ';' that ends a domain. {@link DN#toString()} escapes a ';' of the base DN
+   * as "\;", so a ';' preceded by an unescaped backslash belongs to the base DN. As {@link String#split(String)}
+   * did, empty trailing domains are dropped.
+   */
+  private static List<String> splitDomains(String multiDomainServerState)
+  {
+    final List<String> domains = new ArrayList<>();
+    int start = 0;
+    boolean escaped = false;
+    for (int i = 0; i < multiDomainServerState.length(); i++)
+    {
+      final char c = multiDomainServerState.charAt(i);
+      if (escaped)
+      {
+        escaped = false;
+      }
+      else if (c == '\\')
+      {
+        escaped = true;
+      }
+      else if (c == ';')
+      {
+        domains.add(multiDomainServerState.substring(start, i));
+        start = i + 1;
+      }
+    }
+    domains.add(multiDomainServerState.substring(start));
+    while (!domains.isEmpty() && domains.get(domains.size() - 1).isEmpty())
+    {
+      domains.remove(domains.size() - 1);
+    }
+    return domains;
+  }
 }

--
Gitblit v1.10.0