This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new c55ae88f4d TIKA-4902: fix locale issue (#3227)
c55ae88f4d is described below
commit c55ae88f4d69aa2dd8ad9c89f4280b8c6a1b0c0c
Author: Tim Allison <[email protected]>
AuthorDate: Thu Sep 24 06:30:12 2026 -0400
TIKA-4902: fix locale issue (#3227)
---
.../tika/parser/image/ImageMetadataExtractor.java | 56 +++++++++++++++++++---
.../image/ImageMetadataExtractorLocaleTest.java | 41 ++++++++++++++++
.../parser/image/ImageMetadataExtractorTest.java | 23 ++++++++-
3 files changed, 112 insertions(+), 8 deletions(-)
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
index 6fa3299e3d..a23684d12b 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
@@ -384,12 +384,15 @@ public class ImageMetadataExtractor {
if (MetadataFields.isMetadataField(name)) {
continue;
}
+ int iccType = directory instanceof IccDirectory ?
+ iccTagDataType(directory, tag.getTagType()) : 0;
// decided before getDescription(): formatting a curve is
the whole cost
- if (!includeIccCurvesAndLuts && directory instanceof
IccDirectory &&
- isIccCurveOrLut(directory, tag.getTagType())) {
+ if (!includeIccCurvesAndLuts && isIccCurveOrLut(iccType)) {
continue;
}
- String description = tag.getDescription();
+ String description = iccType == ICC_TYPE_CURV ?
+
formatIccCurve(directory.getByteArray(tag.getTagType())) :
+ tag.getDescription();
if (description != null) {
String value = description.trim();
if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
@@ -409,17 +412,20 @@ public class ImageMetadataExtractor {
}
}
- private static boolean isIccCurveOrLut(Directory directory, int
tagType) {
+ private static int iccTagDataType(Directory directory, int tagType) {
// header fields sit below the printable-ASCII range of tag-table
signatures
if (tagType <= 0x20202020 || tagType >= 0x7a7a7a7a) {
- return false;
+ return 0;
}
byte[] data = directory.getByteArray(tagType);
if (data == null || data.length < 4) {
- return false;
+ return 0;
}
- int type = ((data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) |
+ return ((data[0] & 0xff) << 24) | ((data[1] & 0xff) << 16) |
((data[2] & 0xff) << 8) | (data[3] & 0xff);
+ }
+
+ private static boolean isIccCurveOrLut(int type) {
switch (type) {
case ICC_TYPE_CURV:
case ICC_TYPE_PARA:
@@ -432,6 +438,42 @@ public class ImageMetadataExtractor {
return false;
}
}
+
+ // metadata-extractor 2.21.0 formats curv in the default locale: "0,"
under de_DE
+ static String formatIccCurve(byte[] data) {
+ if (data.length < 12) {
+ return null;
+ }
+ ByteBuffer b = ByteBuffer.wrap(data);
+ long count = Integer.toUnsignedLong(b.getInt(8));
+ if (count > (data.length - 12) / 2) {
+ return null;
+ }
+ // per the ICC curveType: no entries is identity, one entry is a
u8Fixed8 gamma
+ if (count == 0) {
+ return "1.0";
+ }
+ if (count == 1) {
+ return formatIccNumber((b.getShort(12) & 0xffff) / 256.0);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(formatIccNumber((b.getShort(12 + i * 2) & 0xffff) /
65535.0));
+ }
+ return sb.toString();
+ }
+
+ private static String formatIccNumber(double value) {
+ String v = String.format(Locale.ROOT, "%.7f", value);
+ int end = v.length();
+ while (end > v.indexOf('.') + 2 && v.charAt(end - 1) == '0') {
+ end--;
+ }
+ return v.substring(0, end);
+ }
}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorLocaleTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorLocaleTest.java
new file mode 100644
index 0000000000..a4e015cb25
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorLocaleTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tika.parser.image;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Locale;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Isolated;
+
+// sets the JVM-wide default locale, so it must not overlap other test classes
+@Isolated
+public class ImageMetadataExtractorLocaleTest {
+
+ @Test
+ public void testIccCurveFormattingIgnoresDefaultLocale() {
+ Locale defaultLocale = Locale.getDefault();
+ try {
+ Locale.setDefault(Locale.GERMANY);
+ assertEquals("0.0, 0.4999924, 1.0",
ImageMetadataExtractor.CopyUnknownFieldsHandler
+ .formatIccCurve(ImageMetadataExtractorTest.iccCurve(3)));
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
index 7bc751c9b5..9f8b0a3ec8 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java
@@ -193,6 +193,18 @@ public class ImageMetadataExtractorTest {
assertEquals("0.0, 1.0", metadata.get(ImageMetadataExtractor.ICC_NS +
"Red TRC"));
}
+ @Test
+ public void testIccCurveSpecialCounts() {
+ assertEquals("1.0",
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(
+ iccCurveRaw(0)));
+ assertEquals("1.0",
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(
+ iccCurveRaw(1, (short) 0x0100)));
+ assertEquals("2.1992188",
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(
+ iccCurveRaw(1, (short) 0x0233)));
+ assertEquals("0.0, 0.4999924, 1.0",
+
ImageMetadataExtractor.CopyUnknownFieldsHandler.formatIccCurve(iccCurve(3)));
+ }
+
//ICC 'desc' tag: type, reserved, byte count including the NUL, ASCII, NUL
private static byte[] iccDesc(String text) {
byte[] ascii = text.getBytes(StandardCharsets.US_ASCII);
@@ -203,7 +215,16 @@ public class ImageMetadataExtractorTest {
}
//ICC 'curv' tag: type, reserved, count, count uint16 samples spread over
0..1
- private static byte[] iccCurve(int count) {
+ static byte[] iccCurveRaw(int count, short... entries) {
+ ByteBuffer b = ByteBuffer.allocate(12 + 2 * entries.length);
+
b.put("curv".getBytes(StandardCharsets.US_ASCII)).putInt(0).putInt(count);
+ for (short e : entries) {
+ b.putShort(e);
+ }
+ return b.array();
+ }
+
+ static byte[] iccCurve(int count) {
ByteBuffer b = ByteBuffer.allocate(12 + 2 * count);
b.put("curv".getBytes(StandardCharsets.US_ASCII)).putInt(0).putInt(count);
for (int i = 0; i < count; i++) {