Skip to content

Commit d66da4f

Browse files
authored
1 parent 945378a commit d66da4f

3 files changed

Lines changed: 78 additions & 73 deletions

File tree

  • tika-core/src/main/java/org/apache/tika/detect
  • tika-parsers/tika-parsers-standard/tika-parsers-standard-modules
    • tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft
    • tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/dbf

tika-core/src/main/java/org/apache/tika/detect/MetadataCharsetDetector.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
* HTTP/MIME Content-Type header (e.g. {@code text/html; charset=UTF-8}).</li>
4141
* <li>{@link TikaCoreProperties#CONTENT_TYPE_HINT} — the {@code charset} parameter
4242
* of a content-type a source <em>claimed</em> for the bytes (e.g. an HTML
43-
* {@code <meta>} tag, or a zip entry's UTF-8 (EFS) flag). A hint, not a verdict.</li>
43+
* {@code <meta>} tag, or a zip entry's UTF-8 (EFS) flag). A hint, not a verdict.
44+
* This key is only consulted when {@link MetadataCharsetDetector} is included in
45+
* the active {@link org.apache.tika.detect.EncodingDetector} chain.</li>
4446
* <li>{@link Metadata#CONTENT_ENCODING} — a bare charset label set by parsers
4547
* such as {@code RFC822Parser}, which splits Content-Type into a bare
4648
* media-type key and a separate charset key.</li>

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/OutlookExtractor.java

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
import org.slf4j.LoggerFactory;
6969
import org.xml.sax.SAXException;
7070

71-
import org.apache.tika.detect.CharsetSupersets;
71+
import org.apache.tika.detect.DefaultEncodingDetector;
72+
import org.apache.tika.detect.EncodingDetector;
7273
import org.apache.tika.detect.EncodingResult;
7374
import org.apache.tika.exception.TikaException;
7475
import org.apache.tika.extractor.EmbeddedDocumentUtil;
@@ -81,14 +82,11 @@
8182
import org.apache.tika.metadata.TikaCoreProperties;
8283
import org.apache.tika.parser.ParseContext;
8384
import org.apache.tika.parser.Parser;
84-
import org.apache.tika.parser.html.HtmlEncodingDetector;
8585
import org.apache.tika.parser.html.JSoupParser;
8686
import org.apache.tika.parser.mailcommons.MailDateParser;
8787
import org.apache.tika.parser.microsoft.msg.ExtendedMetadataExtractor;
8888
import org.apache.tika.parser.microsoft.rtf.RTFParser;
8989
import org.apache.tika.parser.microsoft.rtf.jflex.RTFHtmlDecapsulator;
90-
import org.apache.tika.parser.txt.CharsetDetector;
91-
import org.apache.tika.parser.txt.CharsetMatch;
9290
import org.apache.tika.sax.BodyContentHandler;
9391
import org.apache.tika.sax.EmbeddedContentHandler;
9492
import org.apache.tika.sax.XHTMLContentHandler;
@@ -183,7 +181,7 @@ private static void loadMessageClasses() {
183181
private final DirectoryNode root;
184182
private final MAPIMessage msg;
185183
private final ParseContext parseContext;
186-
HtmlEncodingDetector detector = new HtmlEncodingDetector();
184+
private static final EncodingDetector DEFAULT_ENCODING_DETECTOR = new DefaultEncodingDetector();
187185

188186

189187
public OutlookExtractor(DirectoryNode root, Metadata metadata, ParseContext context) throws TikaException {
@@ -848,92 +846,91 @@ private void guess7BitEncoding(MAPIMessage msg) {
848846
return;
849847
}
850848

849+
// A declared charset (message codepage, else a Content-Type header) is a hint,
850+
// not a verdict -- the detector evaluates it against the raw body bytes below.
851+
String declared = declaredCharset(msg, mainChunks);
852+
853+
// Detect on the raw body bytes -- the HTML binary chunk if present, else the
854+
// text body. (msg.getHtmlBody() is an already-decoded String, so detecting its
855+
// re-encoded bytes would just report the re-encoding charset.)
856+
byte[] body = null;
857+
ByteChunk htmlBinary = mainChunks.getHtmlBodyChunkBinary();
858+
if (htmlBinary != null && htmlBinary.getValue() != null) {
859+
body = htmlBinary.getValue();
860+
} else if (mainChunks.getTextBodyChunk() != null) {
861+
body = mainChunks.getTextBodyChunk().getRawValue();
862+
}
863+
864+
EncodingDetector encodingDetector = context.get(EncodingDetector.class);
865+
if (encodingDetector == null) {
866+
encodingDetector = DEFAULT_ENCODING_DETECTOR;
867+
}
868+
869+
if (body != null && body.length > 0) {
870+
Metadata metadata = new Metadata();
871+
if (declared != null) {
872+
metadata.set(TikaCoreProperties.CONTENT_TYPE_HINT,
873+
"text/plain; charset=" + declared);
874+
}
875+
try (TikaInputStream tis = TikaInputStream.get(body)) {
876+
List<EncodingResult> results = encodingDetector.detect(tis, metadata, context);
877+
if (!results.isEmpty() && results.get(0).getConfidence() > 0.35f
878+
&& tryToSet7BitEncoding(msg, results.get(0).getDecodeAs().name())) {
879+
return;
880+
}
881+
} catch (IOException e) {
882+
//swallow
883+
}
884+
}
885+
886+
// No body to adjudicate against (or detection abstained): trust the declaration.
887+
if (declared != null) {
888+
tryToSet7BitEncoding(msg, declared);
889+
}
890+
}
891+
892+
/**
893+
* The charset a 7-bit message declares for itself: its codepage property
894+
* (MESSAGE_CODEPAGE / INTERNET_CPID), else a {@code charset} on a Content-Type
895+
* header. A hint for the detector, not a verdict. {@code null} if none.
896+
*/
897+
private static String declaredCharset(MAPIMessage msg, Chunks mainChunks) {
851898
Map<MAPIProperty, List<PropertyValue>> props = mainChunks.getProperties();
852899
if (props != null) {
853-
// First choice is a codepage property
854-
for (MAPIProperty prop : new MAPIProperty[]{MAPIProperty.MESSAGE_CODEPAGE, MAPIProperty.INTERNET_CPID}) {
900+
for (MAPIProperty prop : new MAPIProperty[]{MAPIProperty.MESSAGE_CODEPAGE,
901+
MAPIProperty.INTERNET_CPID}) {
855902
List<PropertyValue> val = props.get(prop);
856903
if (val != null && val.size() > 0) {
857904
int codepage = ((PropertyValue.LongPropertyValue) val.get(0)).getValue();
858-
String encoding = null;
859905
try {
860-
encoding = CodePageUtil.codepageToEncoding(codepage, true);
861-
} catch (UnsupportedEncodingException e) {
862-
//swallow
863-
}
864-
if (tryToSet7BitEncoding(msg, encoding)) {
865-
return;
906+
String encoding = CodePageUtil.codepageToEncoding(codepage, true);
907+
if (encoding != null && Charset.isSupported(encoding)) {
908+
return encoding;
909+
}
910+
} catch (UnsupportedEncodingException | IllegalArgumentException e) {
911+
//swallow, try the next source
866912
}
867913
}
868914
}
869915
}
870-
871-
// Second choice is a charset on a content type header
872916
try {
873917
String[] headers = msg.getHeaders();
874-
if (headers != null && headers.length > 0) {
875-
// Look for a content type with a charset
876-
Pattern p = Pattern.compile("Content-Type:.*?charset=[\"']?([^;'\"]+)[\"']?", Pattern.CASE_INSENSITIVE);
877-
918+
if (headers != null) {
919+
Pattern p = Pattern.compile(
920+
"Content-Type:.*?charset=[\"']?([^;'\"]+)[\"']?", Pattern.CASE_INSENSITIVE);
878921
for (String header : headers) {
879922
if (header.startsWith("Content-Type")) {
880923
Matcher m = p.matcher(header);
881924
if (m.matches()) {
882-
// Found it! Tell all the string chunks
883-
String charset = m.group(1);
884-
if (tryToSet7BitEncoding(msg, charset)) {
885-
return;
886-
}
925+
return m.group(1);
887926
}
888927
}
889928
}
890929
}
891930
} catch (ChunkNotFoundException e) {
892931
//swallow
893932
}
894-
895-
// Nothing suitable in the headers, try HTML
896-
// TODO: do we need to replicate this in Tika? If we wind up
897-
// parsing the html version of the email, this is duplicative??
898-
// Or do we need to reset the header strings based on the html
899-
// meta header if there is no other information?
900-
try {
901-
String html = msg.getHtmlBody();
902-
if (html != null && html.length() > 0) {
903-
Charset charset = null;
904-
try (TikaInputStream tis = TikaInputStream.get(html.getBytes(UTF_8))) {
905-
List<EncodingResult> encResults =
906-
detector.detect(tis, EMPTY_METADATA, context);
907-
charset = encResults.isEmpty() ? null : encResults.get(0).getDecodeAs();
908-
} catch (IOException e) {
909-
//swallow
910-
}
911-
if (charset != null && tryToSet7BitEncoding(msg, charset.name())) {
912-
return;
913-
}
914-
}
915-
} catch (ChunkNotFoundException e) {
916-
//swallow
917-
}
918-
919-
//absolute last resort, try charset detector
920-
StringChunk text = mainChunks.getTextBodyChunk();
921-
if (text != null) {
922-
CharsetDetector detector = new CharsetDetector();
923-
detector.setText(text.getRawValue());
924-
CharsetMatch match = detector.detect();
925-
if (match != null && match.getConfidence() > 35) {
926-
String charsetName = match.getName();
927-
try {
928-
charsetName = CharsetSupersets.decodeAs(Charset.forName(charsetName)).name();
929-
} catch (IllegalArgumentException e) {
930-
//ICU name not a resolvable Java charset; use as-is
931-
}
932-
if (tryToSet7BitEncoding(msg, charsetName)) {
933-
return;
934-
}
935-
}
936-
}
933+
return null;
937934
}
938935

939936
private boolean tryToSet7BitEncoding(MAPIMessage msg, String charsetName) {

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/dbf/DBFParser.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import org.apache.tika.config.TikaComponent;
3333
import org.apache.tika.detect.CharsetSupersets;
34+
import org.apache.tika.detect.DefaultEncodingDetector;
3435
import org.apache.tika.detect.EncodingDetector;
3536
import org.apache.tika.detect.EncodingResult;
3637
import org.apache.tika.exception.TikaException;
@@ -40,7 +41,6 @@
4041
import org.apache.tika.mime.MediaType;
4142
import org.apache.tika.parser.ParseContext;
4243
import org.apache.tika.parser.Parser;
43-
import org.apache.tika.parser.txt.Icu4jEncodingDetector;
4444
import org.apache.tika.sax.XHTMLContentHandler;
4545

4646
/**
@@ -57,6 +57,7 @@ public class DBFParser implements Parser {
5757
private static final int ROWS_TO_BUFFER_FOR_CHARSET_DETECTION = 10;
5858
private static final int MAX_CHARS_FOR_CHARSET_DETECTION = 20000;
5959
private static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
60+
private static final EncodingDetector DEFAULT_ENCODING_DETECTOR = new DefaultEncodingDetector();
6061

6162
private static final Set<MediaType> SUPPORTED_TYPES =
6263
Collections.singleton(MediaType.application("x-dbf"));
@@ -140,11 +141,16 @@ private Charset getCharset(List<DBFRow> firstRows, DBFFileHeader header, ParseCo
140141
}
141142
byte[] bytes = bos.toByteArray();
142143
if (bytes.length > 20) {
143-
EncodingDetector detector = new Icu4jEncodingDetector();
144+
EncodingDetector detector = parseContext.get(EncodingDetector.class);
145+
if (detector == null) {
146+
detector = DEFAULT_ENCODING_DETECTOR;
147+
}
144148
try (TikaInputStream tis = TikaInputStream.get(bytes)) {
145149
List<EncodingResult> results =
146-
detector.detect(TikaInputStream.get(bytes), new Metadata(), parseContext);
147-
charset = results.isEmpty() ? null : results.get(0).getCharset();
150+
detector.detect(tis, new Metadata(), parseContext);
151+
if (!results.isEmpty() && results.get(0).getCharset() != null) {
152+
charset = results.get(0).getCharset();
153+
}
148154
}
149155
}
150156
return charset;

0 commit comments

Comments
 (0)