This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 0a99327b6bc4ce8d4f1cc8ece449d89996ad7ff0 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 b2e05900b4..345b34d93f 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; @@ -109,10 +111,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) { @@ -125,7 +128,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]
