You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2023/06/21 17:10:31 UTC
[tika] branch main updated: TIKA-4091 (#1203)
This is an automated email from the ASF dual-hosted git repository.
tallison 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 9b1228de9 TIKA-4091 (#1203)
9b1228de9 is described below
commit 9b1228de91bb64e79171db8b492ced6977931964
Author: Tim Allison <ta...@apache.org>
AuthorDate: Wed Jun 21 13:10:25 2023 -0400
TIKA-4091 (#1203)
* TIKA-4091 -- improve OLE entry processing to be case insensitive where possible
---
.../detect/microsoft/POIFSContainerDetector.java | 236 +++++++++++++++------
.../apache/tika/parser/microsoft/OfficeParser.java | 28 ++-
.../tika/parser/microsoft/SummaryExtractor.java | 18 +-
.../tika/parser/microsoft/OLE2CasingTest.java | 73 +++++++
.../casing/protected_normal_case.docx | Bin 0 -> 17920 bytes
.../casing/protected_upper_case.docx | Bin 0 -> 17920 bytes
.../test-documents/casing/simple_lower_case.doc | Bin 0 -> 27136 bytes
.../test-documents/casing/simple_normal_case.doc | Bin 0 -> 27136 bytes
.../test-documents/casing/simple_upper_case.doc | Bin 0 -> 27136 bytes
9 files changed, 276 insertions(+), 79 deletions(-)
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/detect/microsoft/POIFSContainerDetector.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/detect/microsoft/POIFSContainerDetector.java
index 6d082cc4c..1cc47ba13 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/detect/microsoft/POIFSContainerDetector.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/detect/microsoft/POIFSContainerDetector.java
@@ -26,6 +26,7 @@ import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
@@ -43,6 +44,7 @@ import org.apache.tika.detect.Detector;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.microsoft.OfficeParser;
/**
* A detector that works on a POIFS OLE2 document
@@ -153,6 +155,69 @@ public class POIFSContainerDetector implements Detector {
* Serial version UID
*/
private static final long serialVersionUID = -3028021741663605293L;
+
+ //We need to have uppercase for finding/comparison, but we want to maintain
+ //the most common general casing for these items
+
+ private static final String ENCRYPTED_PACKAGE = "EncryptedPackage".toUpperCase(Locale.ROOT);
+
+ private static final String ENCRYPTED_INFO = "EncryptionInfo".toUpperCase(Locale.ROOT);
+
+ private static final String SW_DOC_CONTENT_MGR = "SwDocContentMgr".toUpperCase(Locale.ROOT);
+
+ private static final String SW_DOC_MGR_TEMP_STORAGE = "SwDocMgrTempStorage".toUpperCase(Locale.ROOT);
+
+ private static final String STAR_CALC_DOCUMENT = "StarCalcDocument".toUpperCase(Locale.ROOT);
+
+ private static final String STAR_WRITER_DOCUMENT = "StarWriterDocument".toUpperCase(Locale.ROOT);
+
+ private static final String STAR_DRAW_DOCUMENT_3 = "StarDrawDocument3".toUpperCase(Locale.ROOT);
+
+ private static final String WKS_SSWORK_BOOK = "WksSSWorkBook".toUpperCase(Locale.ROOT);
+
+ private static final String DATA_SPACES = "\u0006DataSpaces".toUpperCase(Locale.ROOT);
+
+ private static final String DRM_ENCRYPTED_DATA_SPACE = "DRMEncryptedDataSpace".toUpperCase(Locale.ROOT);
+
+ private static final String DRM_DATA_SPACE = "\tDRMDataSpace".toUpperCase(Locale.ROOT);
+
+ private static final String WORD_DOCUMENT = "WordDocument".toUpperCase(Locale.ROOT);
+
+ private static final String QUILL = "Quill".toUpperCase(Locale.ROOT);
+
+ private static final String POWERPOINT_DOCUMENT = "PowerPoint Document".toUpperCase(Locale.ROOT);
+
+ private static final String VISIO_DOCUMENT = "VisioDocument".toUpperCase(Locale.ROOT);
+
+ private static final String OLE10_NATIVE_STRING = "\u0001Ole10Native".toUpperCase(Locale.ROOT);
+
+ private static final String MAT_OST = "MatOST".toUpperCase(Locale.ROOT);
+
+ private static final String CONTENTS = "CONTENTS".toUpperCase(Locale.ROOT);
+
+ private static final String SPELLING = "SPELLING".toUpperCase(Locale.ROOT);
+
+ private static final String OBJ_INFO = "\u0003ObjInfo".toUpperCase(Locale.ROOT);
+
+ private static final String COMP_OBJ_STRING = "\u0001CompObj".toUpperCase(Locale.ROOT);
+
+ private static final String PROPS = "Props".toUpperCase(Locale.ROOT);
+
+ private static final String PROPS_9 = "Props9".toUpperCase(Locale.ROOT);
+
+ private static final String PROPS_12 = "Props12".toUpperCase(Locale.ROOT);
+
+ private static final String EQUATION_NATIVE = "Equation Native".toUpperCase(Locale.ROOT);
+
+ private static final String LAYER = "Layer".toUpperCase(Locale.ROOT);
+
+ private static final String DGN_MF = "Dgn~Mf".toUpperCase(Locale.ROOT);
+
+ private static final String DGN_S = "Dgn~S".toUpperCase(Locale.ROOT);
+ private static final String DGN_H = "Dgn~H".toUpperCase(Locale.ROOT);
+
+ private static final String SUBSTG_1 = "__substg1.0_".toUpperCase(Locale.ROOT);
+
/**
* An ASCII String "StarImpress"
*/
@@ -201,50 +266,29 @@ public class POIFSContainerDetector implements Detector {
* detection may need access to the root {@link DirectoryEntry} of that file
* for best results. The entry can be given as a second, optional argument.
*
- * @param names
+ * <p/>
+ * Following
+ *
+ * <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/60fe8611-66c3-496b-b70d-a504c94c9ace">2.6.1 of MS-CFB </a>,
+ * The detection is performed on case insensitive entry names.
+ *
+ * @param anyCaseNames
* @param root
* @return
*/
- public static MediaType detect(Set<String> names, DirectoryEntry root) {
- if (names == null || names.size() == 0) {
+ public static MediaType detect(Set<String> anyCaseNames, DirectoryEntry root) {
+ if (anyCaseNames == null || anyCaseNames.size() == 0) {
return OLE;
}
- //figure out if encrypted/pw protected first
- if (names.contains("\u0006DataSpaces")) {
- //OLE2 drm encrypted -- TIKA-3666
- if (findRecursively(root, "\tDRMDataSpace", 0, 10)) {
- return DRM_ENCRYPTED;
- }
- }
- if (names.contains("EncryptedPackage")) {
- if (names.contains("EncryptionInfo")) {
- // This is a protected OOXML document, which is an OLE2 file
- // with an Encrypted Stream which holds the OOXML data
- // Without decrypting the stream, we can't tell what kind of
- // OOXML file we have. Return a general OOXML Protected type,
- // and hope the name based detection can guess the rest!
-
- // This is the standard issue method of encryption for ooxml and
- // is supported by POI
-
- //Until Tika 1.23, we also required: && names.contains("\u0006DataSpaces")
- //See TIKA-2982
- return OOXML_PROTECTED;
- } else if (names.contains("\u0006DataSpaces")) {
- //Try to look for the DRMEncrypted type (TIKA-3666); as of 5.2.0, this is not
- // supported by POI, but we should still detect it.
-
- //Do we also want to look for "DRMEncryptedTransform"?
- if (findRecursively(root, "DRMEncryptedDataSpace", 0, 10)) {
- return DRM_ENCRYPTED;
- }
- }
+ Set<String> ucNames = upperCase(anyCaseNames);
+ MediaType mediaType = checkEncrypted(ucNames, root);
+ if (mediaType != null) {
+ return mediaType;
}
-
for (String workbookEntryName : InternalWorkbook.WORKBOOK_DIR_ENTRY_NAMES) {
- if (names.contains(workbookEntryName)) {
+ if (ucNames.contains(workbookEntryName)) {
MediaType tmp = processCompObjFormatType(root);
if (tmp.equals(MS_GRAPH_CHART)) {
return MS_GRAPH_CHART;
@@ -252,14 +296,14 @@ public class POIFSContainerDetector implements Detector {
return XLS;
}
}
- if (names.contains("SwDocContentMgr") && names.contains("SwDocMgrTempStorage")) {
+ if (ucNames.contains(SW_DOC_CONTENT_MGR) && ucNames.contains(SW_DOC_MGR_TEMP_STORAGE)) {
return SLDWORKS;
- } else if (names.contains("StarCalcDocument")) {
+ } else if (ucNames.contains(STAR_CALC_DOCUMENT)) {
// Star Office Calc
return SDC;
- } else if (names.contains("StarWriterDocument")) {
+ } else if (ucNames.contains(STAR_WRITER_DOCUMENT)) {
return SDW;
- } else if (names.contains("StarDrawDocument3")) {
+ } else if (ucNames.contains(STAR_DRAW_DOCUMENT_3)) {
if (root == null) {
/*
* This is either StarOfficeDraw or StarOfficeImpress, we have
@@ -273,33 +317,33 @@ public class POIFSContainerDetector implements Detector {
} else {
return processCompObjFormatType(root);
}
- } else if (names.contains("WksSSWorkBook")) {
+ } else if (ucNames.contains(WKS_SSWORK_BOOK)) {
// This check has to be before names.contains("Workbook")
// Works 7.0 spreadsheet files contain both
// we want to avoid classifying this as Excel
return XLR;
- } else if (names.contains("Book")) {
+ } else if (ucNames.contains("BOOK")) {
// Excel 95 or older, we won't be able to parse this....
return XLS;
- } else if (names.contains("WordDocument")) {
+ } else if (ucNames.contains(WORD_DOCUMENT)) {
return DOC;
- } else if (names.contains("Quill")) {
+ } else if (ucNames.contains(QUILL)) {
return PUB;
- } else if (names.contains("PowerPoint Document")) {
+ } else if (ucNames.contains(POWERPOINT_DOCUMENT)) {
return PPT;
- } else if (names.contains("VisioDocument")) {
+ } else if (ucNames.contains(VISIO_DOCUMENT)) {
return VSD;
- } else if (names.contains("\u0001Ole10Native")) {
+ } else if (ucNames.contains(OLE10_NATIVE_STRING)) {
return OLE10_NATIVE;
- } else if (names.contains("MatOST")) {
+ } else if (ucNames.contains(MAT_OST)) {
// this occurs on older Works Word Processor files (versions 3.0 and 4.0)
return WPS;
- } else if (names.contains("CONTENTS") && names.contains("SPELLING")) {
+ } else if (ucNames.contains(CONTENTS) && ucNames.contains(SPELLING)) {
// Newer Works files
return WPS;
- } else if (names.contains("Contents") && names.contains("\u0003ObjInfo")) {
+ } else if (ucNames.contains(CONTENTS) && ucNames.contains(OBJ_INFO)) {
return COMP_OBJ;
- } else if (names.contains("CONTENTS") && names.contains("\u0001CompObj")) {
+ } else if (ucNames.contains(CONTENTS) && ucNames.contains(COMP_OBJ_STRING)) {
// CompObj is a general kind of OLE2 embedding, but this may be an old Works file
// If we have the Directory, check
if (root != null) {
@@ -314,33 +358,33 @@ public class POIFSContainerDetector implements Detector {
// Assume it's a general CompObj embedded resource
return COMP_OBJ;
}
- } else if (names.contains("CONTENTS")) {
+ } else if (ucNames.contains(CONTENTS)) {
// CONTENTS without SPELLING nor CompObj normally means some sort
// of embedded non-office file inside an OLE2 document
// This is most commonly triggered on nested directories
return OLE;
- } else if (names.contains("\u0001CompObj") &&
- (names.contains("Props") || names.contains("Props9") ||
- names.contains("Props12"))) {
+ } else if (ucNames.contains(COMP_OBJ_STRING) &&
+ (ucNames.contains(PROPS) || ucNames.contains(PROPS_9) ||
+ ucNames.contains(PROPS_12))) {
// Could be Project, look for common name patterns
- for (String name : names) {
+ for (String name : ucNames) {
if (mppDataMatch.matcher(name).matches()) {
return MPP;
}
}
- } else if (names.contains("Equation Native")) {
+ } else if (ucNames.contains(EQUATION_NATIVE)) {
return MS_EQUATION;
- } else if (names.contains("Layer")) {
+ } else if (ucNames.contains(LAYER)) {
//in one test file, also saw LayerSmallImage and LayerLargeImage
//maybe add those if we get false positives?
//in other test files there was a single entry for "Layer"
return ESRI_LAYER;
- } else if (names.contains("Dgn~Mf") && names.contains("Dgn~S") &&
- names.contains("Dgn~H")) {
+ } else if (ucNames.contains(DGN_MF) && ucNames.contains(DGN_S) &&
+ ucNames.contains(DGN_H)) {
return DGN_8;
} else {
- for (String name : names) {
- if (name.startsWith("__substg1.0_")) {
+ for (String name : ucNames) {
+ if (name.startsWith(SUBSTG_1)) {
return MSG;
}
}
@@ -351,12 +395,64 @@ public class POIFSContainerDetector implements Detector {
return OLE;
}
+ private static MediaType checkEncrypted(Set<String> ucNames, DirectoryEntry root) {
+ //figure out if encrypted/pw protected first
+ if (ucNames.contains(DATA_SPACES)) {
+ //OLE2 drm encrypted -- TIKA-3666
+ if (findRecursively(root, DRM_DATA_SPACE, 0, 10)) {
+ return DRM_ENCRYPTED;
+ }
+ }
+
+ if (ucNames.contains(ENCRYPTED_PACKAGE)) {
+ if (ucNames.contains(ENCRYPTED_INFO)) {
+ // This is a protected OOXML document, which is an OLE2 file
+ // with an Encrypted Stream which holds the OOXML data
+ // Without decrypting the stream, we can't tell what kind of
+ // OOXML file we have. Return a general OOXML Protected type,
+ // and hope the name based detection can guess the rest!
+
+ // This is the standard issue method of encryption for ooxml and
+ // is supported by POI
+
+ //Until Tika 1.23, we also required: && names.contains("\u0006DataSpaces")
+ //See TIKA-2982
+ return OOXML_PROTECTED;
+ } else if (ucNames.contains(DATA_SPACES)) {
+ //Try to look for the DRMEncrypted type (TIKA-3666); as of 5.2.0, this is not
+ // supported by POI, but we should still detect it.
+
+ //Do we also want to look for "DRMEncryptedTransform"?
+ if (findRecursively(root, DRM_ENCRYPTED_DATA_SPACE, 0, 10)) {
+ return DRM_ENCRYPTED;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static Set<String> upperCase(Set<String> names) {
+ Set<String> uc = new HashSet<>(names.size());
+ for (String s : names) {
+ uc.add(s.toUpperCase(Locale.ROOT));
+ }
+ return uc;
+ }
+
+ /**
+ *
+ * @param entry entry to search
+ * @param targetName Upper cased target name
+ * @param depth current depth
+ * @param maxDepth maximum allowed depth
+ * @return
+ */
private static boolean findRecursively(Entry entry, String targetName, int depth,
int maxDepth) {
if (entry == null) {
return false;
}
- if (entry.getName().equals(targetName)) {
+ if (entry.getName().toUpperCase(Locale.ROOT).equals(targetName)) {
return true;
}
if (depth >= maxDepth) {
@@ -381,13 +477,9 @@ public class POIFSContainerDetector implements Detector {
*/
private static MediaType processCompObjFormatType(DirectoryEntry root) {
try {
-
- if (!root.hasEntry("\u0001CompObj")) {
- return OLE;
- }
- Entry e = root.getEntry("\u0001CompObj");
- if (e != null && e.isDocumentEntry()) {
- DocumentNode dn = (DocumentNode) e;
+ Entry entry = OfficeParser.getUCEntry(root, COMP_OBJ_STRING);
+ if (entry != null && entry.isDocumentEntry()) {
+ DocumentNode dn = (DocumentNode) entry;
DocumentInputStream stream = new DocumentInputStream(dn);
byte[] bytes = IOUtils.toByteArray(stream);
/*
@@ -421,6 +513,7 @@ public class POIFSContainerDetector implements Detector {
return OLE;
}
+
// poor man's search for byte arrays, replace with some library call if
// you know one without adding new dependencies
private static boolean arrayContains(byte[] larger, byte[] smaller) {
@@ -441,6 +534,11 @@ public class POIFSContainerDetector implements Detector {
return false;
}
+ /**
+ * These are the literal top level names in the root. These are not uppercased
+ * @param root
+ * @return
+ */
private static Set<String> getTopLevelNames(DirectoryNode root) {
Set<String> names = new HashSet<>();
for (Entry entry : root) {
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java
index 8d938fdbe..e862260f6 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OfficeParser.java
@@ -16,6 +16,7 @@
*/
package org.apache.tika.parser.microsoft;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@@ -24,6 +25,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -240,10 +242,10 @@ public class OfficeParser extends AbstractOfficeParser {
extractor.parse(xhtml);
break;
case ENCRYPTED:
- EncryptionInfo info = new EncryptionInfo(root);
- Decryptor d = Decryptor.getInstance(info);
try {
+ EncryptionInfo info = new EncryptionInfo(root);
+ Decryptor d = Decryptor.getInstance(info);
// By default, use the default Office Password
String password = Decryptor.DEFAULT_PASSWORD;
@@ -271,6 +273,10 @@ public class OfficeParser extends AbstractOfficeParser {
}
} catch (GeneralSecurityException ex) {
throw new EncryptedDocumentException(ex);
+ } catch (FileNotFoundException ex) {
+ //this can happen because POI may not support case-insensitive ole2 object
+ //lookups
+ throw new EncryptedDocumentException(ex);
}
break;
case DRMENCRYPTED:
@@ -351,4 +357,22 @@ public class OfficeParser extends AbstractOfficeParser {
}
}
+ /**
+ * Looks for entry within root (non-recursive) that has an upper-cased
+ * name that equals ucTarget
+ * @param root
+ * @param ucTarget
+ * @return
+ */
+ public static Entry getUCEntry(DirectoryEntry root, String ucTarget) {
+ Iterator<Entry> it = root.getEntries();
+ while (it.hasNext()) {
+ Entry e = it.next();
+ if (e.getName().toUpperCase(Locale.US).equals(ucTarget)) {
+ return e;
+ }
+ }
+ return null;
+ }
+
}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java
index e71812204..89f1913fa 100644
--- a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/SummaryExtractor.java
@@ -16,11 +16,11 @@
*/
package org.apache.tika.parser.microsoft;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
import org.apache.poi.hpsf.CustomProperties;
@@ -53,10 +53,11 @@ import org.apache.tika.utils.StringUtils;
public class SummaryExtractor {
private static final Logger LOG = LoggerFactory.getLogger(AbstractPOIFSExtractor.class);
- private static final String SUMMARY_INFORMATION = SummaryInformation.DEFAULT_STREAM_NAME;
+ private static final String SUMMARY_INFORMATION =
+ SummaryInformation.DEFAULT_STREAM_NAME.toUpperCase(Locale.US);
private static final String DOCUMENT_SUMMARY_INFORMATION =
- DocumentSummaryInformation.DEFAULT_STREAM_NAME;
+ DocumentSummaryInformation.DEFAULT_STREAM_NAME.toUpperCase(Locale.US);
private final Metadata metadata;
@@ -99,18 +100,19 @@ public class SummaryExtractor {
private void parseSummaryEntryIfExists(DirectoryNode root, String entryName)
throws IOException, TikaException {
try {
- if (!root.hasEntry(entryName)) {
- return;
- }
DocumentEntry entry = null;
try {
- entry = (DocumentEntry) root.getEntry(entryName);
- } catch (FileNotFoundException | IllegalArgumentException e) {
+ entry = (DocumentEntry) OfficeParser.getUCEntry(root, entryName);
+ } catch (IllegalArgumentException e) {
//POI throws these if there is a key in the entries map
//but the entry is null
return;
}
+ if (entry == null) {
+ return;
+ }
+
PropertySet properties = new PropertySet(new DocumentInputStream(entry));
if (properties.isSummaryInformation()) {
parse(new SummaryInformation(properties));
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/OLE2CasingTest.java b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/OLE2CasingTest.java
new file mode 100644
index 000000000..7f38caf76
--- /dev/null
+++ b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/OLE2CasingTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.microsoft;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.TikaTest;
+import org.apache.tika.exception.EncryptedDocumentException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+
+public class OLE2CasingTest extends TikaTest {
+
+ final static Set<String> IGNORE_FIELDS = new HashSet<>();
+
+ static {
+ IGNORE_FIELDS.add(TikaCoreProperties.PARSE_TIME_MILLIS.getName());
+ }
+
+ @Test
+ public void testEncrypted() throws Exception {
+ Assertions.assertThrows(EncryptedDocumentException.class, () -> {
+ getXML("casing/protected_normal_case.docx");
+ });
+ Assertions.assertThrows(EncryptedDocumentException.class, () -> {
+ getXML("casing/protected_upper_case.docx");
+ });
+ }
+
+ @Test
+ @Disabled("until POI can handle case insensitive entry lookups")
+ public void testBasic() throws Exception {
+ List<Metadata> metadataList = getRecursiveMetadata("casing/simple_normal_case.doc");
+ assertCloseEnough(metadataList, getRecursiveMetadata("casing/simple_lower_case.doc"));
+ assertCloseEnough(metadataList, getRecursiveMetadata("casing/simple_upper_case.doc"));
+ }
+
+ private void assertCloseEnough(List<Metadata> expected, List<Metadata> test) {
+ for (int i = 0; i < expected.size(); i++) {
+ assertCloseEnough(expected.get(i), test.get(i));
+ }
+ }
+
+ private void assertCloseEnough(Metadata expected, Metadata test) {
+ for (String n : expected.names()) {
+ if (! IGNORE_FIELDS.contains(n)) {
+ assertArrayEquals(expected.getValues(n), test.getValues(n));
+ }
+ }
+ }
+}
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/protected_normal_case.docx b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/protected_normal_case.docx
new file mode 100644
index 000000000..2c68ef9e0
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/protected_normal_case.docx differ
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/protected_upper_case.docx b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/protected_upper_case.docx
new file mode 100644
index 000000000..20b9fcdad
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/protected_upper_case.docx differ
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_lower_case.doc b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_lower_case.doc
new file mode 100644
index 000000000..f9f2c6ed1
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_lower_case.doc differ
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_normal_case.doc b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_normal_case.doc
new file mode 100644
index 000000000..e79a62102
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_normal_case.doc differ
diff --git a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_upper_case.doc b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_upper_case.doc
new file mode 100644
index 000000000..b09ec382c
Binary files /dev/null and b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/resources/test-documents/casing/simple_upper_case.doc differ