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

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

commit f7619cbf0fd9874489c561b6747bc6b04b732cb0
Author: opencode <[email protected]>
AuthorDate: Thu Oct 8 14:21:57 2026 +0200

    Make SSI #fsize output independent of the JVM default locale
    
    The DecimalFormat instances used by the abbreviated and bytes size
    formats were created with the JVM default locale, so server-generated
    #fsize output varied with the locale, for example a 1234 byte file
    rendered as 1.234 rather than 1,234 on a German JVM and 1.5M rendered
    with a comma decimal separator.
    
    Build the formats with the symbols of the root locale so the output is
    locale independent, consistent with the httpd behavior this code mimics.
    Add a unit test that checks the formatting under a non-default locale.
---
 java/org/apache/catalina/ssi/SSIFsize.java     |   7 +-
 test/org/apache/catalina/ssi/TestSSIFsize.java | 105 +++++++++++++++++++++++++
 2 files changed, 110 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/catalina/ssi/SSIFsize.java 
b/java/org/apache/catalina/ssi/SSIFsize.java
index 85a9647c34..bf3ed42a4e 100644
--- a/java/org/apache/catalina/ssi/SSIFsize.java
+++ b/java/org/apache/catalina/ssi/SSIFsize.java
@@ -20,6 +20,8 @@ package org.apache.catalina.ssi;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
 
 import org.apache.tomcat.util.res.StringManager;
 
@@ -97,10 +99,11 @@ public final class SSIFsize implements SSICommand {
     // We try to mimic httpd here, as we do everywhere.
     // All the 'magic' numbers are from the util_script.c httpd source file.
     // Should use KiB and MiB in output but use k and M for consistency with 
httpd.
+    // The formatting is locale independent since it is server generated 
output.
     private String formatSize(long size, String format) {
         String retString;
         if (format.equalsIgnoreCase("bytes")) {
-            DecimalFormat decimalFormat = new DecimalFormat("#,##0");
+            DecimalFormat decimalFormat = new DecimalFormat("#,##0", 
DecimalFormatSymbols.getInstance(Locale.ROOT));
             retString = decimalFormat.format(size);
         } else {
             if (size < 0) {
@@ -113,7 +116,7 @@ public final class SSIFsize implements SSICommand {
                 retString = Long.toString((size + 512) / ONE_KIBIBYTE);
                 retString += "k";
             } else if (size < 99 * ONE_MEBIBYTE) {
-                DecimalFormat decimalFormat = new DecimalFormat("0.0M");
+                DecimalFormat decimalFormat = new DecimalFormat("0.0M", 
DecimalFormatSymbols.getInstance(Locale.ROOT));
                 retString = decimalFormat.format(size / (double) ONE_MEBIBYTE);
             } else {
                 retString = Long.toString((size + (529 * ONE_KIBIBYTE)) / 
ONE_MEBIBYTE);
diff --git a/test/org/apache/catalina/ssi/TestSSIFsize.java 
b/test/org/apache/catalina/ssi/TestSSIFsize.java
new file mode 100644
index 0000000000..bd7e33d9ed
--- /dev/null
+++ b/test/org/apache/catalina/ssi/TestSSIFsize.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.ssi;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Locale;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestSSIFsize {
+
+    @Test
+    public void testFormatSizeIsLocaleIndependent() throws Exception {
+        Locale defaultLocale = Locale.getDefault();
+        try {
+            Locale.setDefault(Locale.GERMANY);
+            Assert.assertEquals("1,234", formatSize(1234, "bytes"));
+            Assert.assertEquals(" 1.5M", formatSize(1572864, "abbrev"));
+        } finally {
+            Locale.setDefault(defaultLocale);
+        }
+    }
+
+
+    private String formatSize(long size, String format) throws IOException {
+        SSIMediator mediator = new SSIMediator(new 
TesterSSIExternalResolver(size), 0);
+        mediator.setConfigSizeFmt(format);
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter writer = new PrintWriter(stringWriter);
+        new SSIFsize().process(mediator, "fsize", new String[] { "file" }, new 
String[] { "any" }, writer);
+        writer.flush();
+        return stringWriter.toString();
+    }
+
+    /**
+     * Minimal implementation that provides the bare essentials required for 
the unit tests.
+     */
+    private static class TesterSSIExternalResolver implements 
SSIExternalResolver {
+
+        private final long fileSize;
+
+        TesterSSIExternalResolver(long fileSize) {
+            this.fileSize = fileSize;
+        }
+
+        @Override
+        public void addVariableNames(Collection<String> variableNames) {
+            // NO-OP
+        }
+
+        @Override
+        public String getVariableValue(String name) {
+            return null;
+        }
+
+        @Override
+        public void setVariableValue(String name, String value) {
+            // NO-OP
+        }
+
+        @Override
+        public Date getCurrentDate() {
+            return null;
+        }
+
+        @Override
+        public long getFileSize(String path, boolean virtual) {
+            return fileSize;
+        }
+
+        @Override
+        public long getFileLastModified(String path, boolean virtual) {
+            return 0;
+        }
+
+        @Override
+        public String getFileText(String path, boolean virtual) {
+            return null;
+        }
+
+        @Override
+        public void log(String message, Throwable throwable) {
+            // NO-OP
+        }
+    }
+}


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

Reply via email to