This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 4dfe4ca9924cdbb195070c11bb288a5a1cf755cc
Author: opencode <[email protected]>
AuthorDate: Thu Oct 8 15:42:33 2026 +0200

    Send the server shutdown command as UTF-8 and time out the stop connect
    
    Catalina.stopServer() wrote the configured shutdown string to the
    shutdown port one character at a time with OutputStream.write(int),
    which only emits the low eight bits of each char. Any shutdown string
    containing a character above U+00FF was corrupted in transit, so the
    comparison in StandardServer.await() could never match and
    catalina stop could never succeed. The stop command also opened the
    socket with the no-timeout constructor, so against a shutdown port
    that silently drops packets it blocked for the full operating system
    TCP connect timeout.
    
    The command is now written as UTF-8 bytes and the server side reads
    the raw bytes and decodes them as UTF-8 before matching, so any
    shutdown string works and the two sides agree on the encoding. ASCII
    commands, the overwhelmingly common case, are unaffected. The client
    now connects with a ten second timeout and reports the failure
    instead of blocking indefinitely.
---
 java/org/apache/catalina/core/StandardServer.java | 14 +++++++++----
 java/org/apache/catalina/startup/Catalina.java    | 25 +++++++++++++++++------
 2 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/java/org/apache/catalina/core/StandardServer.java 
b/java/org/apache/catalina/core/StandardServer.java
index 3dca67b94d..428e2de1ea 100644
--- a/java/org/apache/catalina/core/StandardServer.java
+++ b/java/org/apache/catalina/core/StandardServer.java
@@ -18,6 +18,7 @@ package org.apache.catalina.core;
 
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -25,6 +26,7 @@ import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
 import java.util.Random;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
@@ -551,7 +553,7 @@ public final class StandardServer extends 
LifecycleMBeanBase implements Server {
 
                 // Wait for the next connection
                 Socket socket = null;
-                StringBuilder command = new StringBuilder();
+                ByteArrayOutputStream commandBytes = new 
ByteArrayOutputStream();
                 try {
                     InputStream stream;
                     long acceptStartTime = System.nanoTime();
@@ -594,7 +596,7 @@ public final class StandardServer extends 
LifecycleMBeanBase implements Server {
                         if (ch < 32 || ch == 127) {
                             break;
                         }
-                        command.append((char) ch);
+                        commandBytes.write(ch);
                         expected--;
                     }
                 } finally {
@@ -608,13 +610,17 @@ public final class StandardServer extends 
LifecycleMBeanBase implements Server {
                     }
                 }
 
+                // Decode the received bytes as UTF-8, matching the encoding
+                // used by the stop command
+                String command = commandBytes.toString(StandardCharsets.UTF_8);
+
                 // Match against our command string
-                boolean match = command.toString().equals(shutdown);
+                boolean match = command.equals(shutdown);
                 if (match) {
                     log.info(sm.getString("standardServer.shutdownViaPort"));
                     break;
                 } else {
-                    
log.warn(sm.getString("standardServer.invalidShutdownCommand", 
command.toString()));
+                    
log.warn(sm.getString("standardServer.invalidShutdownCommand", command));
                 }
             }
         } finally {
diff --git a/java/org/apache/catalina/startup/Catalina.java 
b/java/org/apache/catalina/startup/Catalina.java
index 1feded95d6..5d514d9d0e 100644
--- a/java/org/apache/catalina/startup/Catalina.java
+++ b/java/org/apache/catalina/startup/Catalina.java
@@ -24,7 +24,9 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.reflect.Constructor;
 import java.net.ConnectException;
+import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -79,6 +81,12 @@ public class Catalina {
      */
     public static final String SERVER_XML = "conf/server.xml";
 
+    /**
+     * Timeout, in milliseconds, for connecting to the shutdown port when
+     * stopping the server.
+     */
+    private static final int SHUTDOWN_CONNECT_TIMEOUT_MS = 10000;
+
     // ----------------------------------------------------- Instance Variables
 
     /**
@@ -772,12 +780,17 @@ public class Catalina {
         // Stop the existing server
         s = getServer();
         if (s.getPortWithOffset() > 0) {
-            try (Socket socket = new Socket(s.getAddress(), 
s.getPortWithOffset());
-                    OutputStream stream = socket.getOutputStream()) {
-                String shutdown = s.getShutdown();
-                for (int i = 0; i < shutdown.length(); i++) {
-                    stream.write(shutdown.charAt(i));
-                }
+            // Use a connect timeout so a shutdown port that silently drops
+            // packets does not block the stop command indefinitely
+            try (Socket socket = new Socket()) {
+                socket.connect(new InetSocketAddress(s.getAddress(), 
s.getPortWithOffset()),
+                        SHUTDOWN_CONNECT_TIMEOUT_MS);
+                OutputStream stream = socket.getOutputStream();
+                // Send the shutdown command as UTF-8 bytes. Writing the
+                // characters one at a time with OutputStream.write(int) would
+                // silently truncate any character above U+00FF and the server
+                // would then never match the configured shutdown string.
+                stream.write(s.getShutdown().getBytes(StandardCharsets.UTF_8));
                 stream.flush();
             } catch (ConnectException ce) {
                 log.error(sm.getString("catalina.stopServer.connectException", 
s.getAddress(),


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

Reply via email to