├── .gitignore ├── .lift.toml ├── .codacy.yml ├── src ├── test │ ├── resources │ │ ├── test-messages │ │ │ ├── attachments.msg │ │ │ ├── plain chain.msg │ │ │ ├── simple sent.msg │ │ │ ├── tst_unicode.msg │ │ │ ├── unsent draft.msg │ │ │ ├── chinese message.msg │ │ │ ├── embedded image.msg │ │ │ ├── testgetmsgAttch.msg │ │ │ ├── nested simple mail.msg │ │ │ ├── simple reply with CC.msg │ │ │ ├── chinese message garbled.msg │ │ │ ├── S_MIME test message signed.msg │ │ │ ├── chinese message un_garbled.msg │ │ │ ├── S_MIME test message encrypted.msg │ │ │ ├── Test at sign in personal From header.msg │ │ │ ├── simple email with TO and CC_multiple.msg │ │ │ ├── simple email with TO and CC_single.msg │ │ │ ├── OutlookMessage with X500 dual address.msg │ │ │ ├── S_MIME test message signed & encrypted.msg │ │ │ ├── attachment with a bracket in the name.msg │ │ │ ├── forward with attachments and embedded images.msg │ │ │ └── HTML mail with replyto and attachment and embedded image.msg │ │ └── log4j2.xml │ └── java │ │ └── org │ │ └── simplejavamail │ │ └── outlookmessageparser │ │ ├── TestUtils.java │ │ ├── model │ │ ├── OutlookMessageTest.java │ │ └── OutlookFileAttachmentTest.java │ │ └── OutlookMessageParserTest.java └── main │ ├── java │ └── org │ │ └── simplejavamail │ │ ├── outlookmessageparser │ │ └── model │ │ │ ├── OutlookAttachment.java │ │ │ ├── OutlookMessageProperty.java │ │ │ ├── OutlookMsgAttachment.java │ │ │ ├── MimeType.java │ │ │ ├── OutlookFieldInformation.java │ │ │ ├── OutlookSmime.java │ │ │ ├── OutlookFileAttachment.java │ │ │ └── OutlookRecipient.java │ │ ├── jakarta │ │ └── mail │ │ │ ├── internet │ │ │ ├── ParseException.java │ │ │ ├── AddressException.java │ │ │ ├── HeaderTokenizer.java │ │ │ └── InternetHeaders.java │ │ │ ├── Header.java │ │ │ ├── Address.java │ │ │ └── MessagingException.java │ │ └── com │ │ └── sun │ │ └── mail │ │ └── util │ │ ├── BEncoderStream.java │ │ ├── QEncoderStream.java │ │ ├── PropUtil.java │ │ ├── LineInputStream.java │ │ ├── QPEncoderStream.java │ │ ├── BASE64EncoderStream.java │ │ └── MailLogger.java │ └── resources │ └── mimetypes.txt ├── .travis.yml ├── spotbugs-exclude.xml ├── .circleci ├── maven-release-settings.xml └── config.yml ├── NOTICE.txt ├── how to release.txt ├── properties-list1.txt ├── RELEASE.txt ├── pom.xml ├── README.md ├── LICENSE-2.0.txt └── properties-list2.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .idea/ 3 | outlook-message-parser.iml 4 | .project 5 | /.settings/* 6 | target/ 7 | -------------------------------------------------------------------------------- /.lift.toml: -------------------------------------------------------------------------------- 1 | ignoreFiles = """ 2 | src/main/java/org/simplejavamail/com/sun/mail/ 3 | src/main/java/org/simplejavamail/jakarta/mail/ 4 | """ 5 | -------------------------------------------------------------------------------- /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - "src/main/java/org/simplejavamail/com/sun/mail/**" 4 | - "src/main/java/org/simplejavamail/jakarta/mail/**" 5 | -------------------------------------------------------------------------------- /src/test/resources/test-messages/attachments.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/attachments.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/plain chain.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/plain chain.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/simple sent.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/simple sent.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/tst_unicode.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/tst_unicode.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/unsent draft.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/unsent draft.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/chinese message.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/chinese message.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/embedded image.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/embedded image.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/testgetmsgAttch.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/testgetmsgAttch.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/nested simple mail.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/nested simple mail.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/simple reply with CC.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/simple reply with CC.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/chinese message garbled.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/chinese message garbled.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/S_MIME test message signed.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/S_MIME test message signed.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/chinese message un_garbled.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/chinese message un_garbled.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/S_MIME test message encrypted.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/S_MIME test message encrypted.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/Test at sign in personal From header.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/Test at sign in personal From header.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/simple email with TO and CC_multiple.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/simple email with TO and CC_multiple.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/simple email with TO and CC_single.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/simple email with TO and CC_single.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/OutlookMessage with X500 dual address.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/OutlookMessage with X500 dual address.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/S_MIME test message signed & encrypted.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/S_MIME test message signed & encrypted.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/attachment with a bracket in the name.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/attachment with a bracket in the name.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/forward with attachments and embedded images.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/forward with attachments and embedded images.msg -------------------------------------------------------------------------------- /src/test/resources/test-messages/HTML mail with replyto and attachment and embedded image.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbottema/outlook-message-parser/HEAD/src/test/resources/test-messages/HTML mail with replyto and attachment and embedded image.msg -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/OutlookAttachment.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | /** 4 | * Interface that defines an attachment. 5 | */ 6 | public interface OutlookAttachment { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/org/simplejavamail/outlookmessageparser/TestUtils.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser; 2 | 3 | public class TestUtils { 4 | public static String normalizeText(String text) { 5 | return text.replaceAll("\\r\\n", "\n").replaceAll("\\r", "\n"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | install: mvn install -DskipFindBugs=true -DskipTests=true -Dmaven.javadoc.skip=true -B -V 4 | script: mvn clean verify -Dmaven.javadoc.skip=true 5 | cache: 6 | directories: 7 | - $HOME/.m2 8 | # whitelist 9 | branches: 10 | only: 11 | - master 12 | - stable 13 | -------------------------------------------------------------------------------- /spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | xmlns="https://github.com/spotbugs/filter/3.0.0" 4 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 | xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd"> 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/OutlookMessageProperty.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | /** 4 | * Represents a message property holding the type of data and the data itself. 5 | */ 6 | public class OutlookMessageProperty { 7 | 8 | /** 9 | * A 4 digit code representing the property type. 10 | */ 11 | private final String clazz; 12 | private final Object data; 13 | private final int size; 14 | 15 | public OutlookMessageProperty(final String clazz, final Object data, final int size) { 16 | this.clazz = clazz; 17 | this.data = data; 18 | this.size = size; 19 | } 20 | 21 | /** 22 | * Bean getter for {@link #clazz}. 23 | */ 24 | public String getClazz() { 25 | return clazz; 26 | } 27 | 28 | public Object getData() { 29 | return data; 30 | } 31 | 32 | public int getSize() { 33 | return size; 34 | } 35 | } -------------------------------------------------------------------------------- /.circleci/maven-release-settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.SERVER_OSSRH_USERNAME} 6 | ${env.SERVER_OSSRH_PASSWORD} 7 | 8 | 9 | 10 | 11 | 12 | gpg 13 | 14 | gpg 15 | ${env.GPG_PASSPHRASE} 16 | 17 | 18 | 19 | 20 | gpg 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/OutlookMsgAttachment.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | /** 4 | * This {@link OutlookAttachment} implementation represents a .msg object attachment. Instead of storing a byte[] of the attachment, this implementation 5 | * provides an embedded {@link OutlookMessage} object. 6 | */ 7 | public class OutlookMsgAttachment implements OutlookAttachment { 8 | 9 | /** 10 | * The encapsulated (attached) outlookMessage. 11 | */ 12 | private final OutlookMessage outlookMessage; 13 | 14 | public OutlookMsgAttachment(final OutlookMessage outlookMessage) { 15 | this.outlookMessage = outlookMessage; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return outlookMessage.toString(); 21 | } 22 | 23 | /** 24 | * Bean getter for {@link #outlookMessage}. 25 | */ 26 | @SuppressWarnings("ElementOnlyUsedFromTestCode") 27 | public OutlookMessage getOutlookMessage() { 28 | return outlookMessage; 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/MimeType.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | import jakarta.activation.MimetypesFileTypeMap; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | class MimeType { 8 | 9 | private static final MimetypesFileTypeMap MIMETYPES_FILE_TYPE_MAP = createMap(); 10 | 11 | /** 12 | * @return a vastly improved mimetype map 13 | */ 14 | private static MimetypesFileTypeMap createMap() { 15 | try (InputStream is = MimeType.class.getClassLoader().getResourceAsStream("mimetypes.txt")) { 16 | return new MimetypesFileTypeMap(is); 17 | } catch (IOException ex) { 18 | throw new RuntimeException(ex); 19 | } 20 | } 21 | 22 | public static String getContentType(String fileName) { 23 | return getContentType(fileName, null); 24 | } 25 | 26 | public static String getContentType(String fileName, String charset) { 27 | String mimeType = MIMETYPES_FILE_TYPE_MAP.getContentType(fileName.toLowerCase()); 28 | if (charset != null && (mimeType.startsWith("text/") || mimeType.contains("javascript"))) { 29 | mimeType += ";charset=" + charset.toLowerCase(); 30 | } 31 | return mimeType; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | ========================================================================= 2 | == NOTICE file for use with the Apache License, Version 2.0 == 3 | ========================================================================= 4 | 5 | Outlook Message Parser 6 | Copyright (C) 2017 Benny Bottema (benny@bennybottema.com) 7 | https://github.com/bbottema/outlook-message-parser 8 | 9 | Originally based on com.auxilii.msgparser:msgparser (http://auxilii.com/msgparser/) 10 | This library was forked with permission and is completely rewritten and improved upon 11 | 12 | The original project was released under GPL3, but express permission was granted to continue 13 | this project under Apache v2. Thanks again Dr. Roman Kurmanowytsch for starting this library 14 | and allowing me to carry it forward! 15 | 16 | This product uses no commercial products. 17 | 18 | This product uses the following external (Open Source) libraries: 19 | 20 | - Apache POI (https://poi.apache.org/) 21 | - Jakarta Activation (https://eclipse-ee4j.github.io/jaf/) 22 | 23 | 24 | ========== email with the relevant part regarding permission to change the license ============ 25 | Hi Benny, 26 | 27 | (..) 28 | 29 | You have my express permission to carry on with the work, publishing it under Apache 2.0 license. 30 | 31 | (..) 32 | 33 | Regards, 34 | Roman -------------------------------------------------------------------------------- /how to release.txt: -------------------------------------------------------------------------------- 1 | Prerequisite: 2 | 3 | You need GPG installed (comes along with GIT installation in the \bin folder) and you need to create a keyring, used for signing artifacts. 4 | If you have an existing key, simply import it: 5 | 6 | gpg --allow-secret-key-import --import .gpg 7 | 8 | To release: 9 | 10 | 1. update release notes and github readme page (don't commit) 11 | 2. remove SNAPSHOT version 12 | 3. mvn -DperformRelease=true clean deploy 13 | (set password in settings.xml or use local pgp key password, for which the public key must have been sent to a public key server, 14 | eg: gpg --keyserver hkp://keyserver.ubuntu.com --send-keys 05AC6403) 15 | server needed in settings.xml (see below) 16 | 4. add new SNAPSHOT version and commit everything 17 | 5. Go to https://oss.sonatype.org and release the artifact so it is submitted to Maven Central 18 | 19 | maven's settings.xml: 20 | 21 | 22 | ossrh 23 | sonatype user 24 | sonatype password 25 | 26 | 27 | To have a global gpg password so that it will use that automatically: 28 | 29 | 30 | 31 | gpg 32 | 33 | gpg 34 | letmein 35 | 36 | 37 | 38 | 39 | gpg 40 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/jakarta/mail/internet/ParseException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.jakarta.mail.internet; 18 | 19 | import org.simplejavamail.jakarta.mail.MessagingException; 20 | 21 | /** 22 | * The exception thrown due to an error in parsing RFC822 23 | * or MIME headers, including multipart bodies. 24 | * 25 | * @author John Mani 26 | */ 27 | 28 | public class ParseException extends MessagingException { 29 | 30 | private static final long serialVersionUID = 7649991205183658089L; 31 | 32 | /** 33 | * Constructs a ParseException with no detail message. 34 | */ 35 | public ParseException() { 36 | super(); 37 | } 38 | 39 | /** 40 | * Constructs a ParseException with the specified detail message. 41 | * @param s the detail message 42 | */ 43 | public ParseException(String s) { 44 | super(s); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/com/sun/mail/util/BEncoderStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.com.sun.mail.util; 18 | 19 | import java.io.*; 20 | 21 | /** 22 | * This class implements a 'B' Encoder as defined by RFC2047 for 23 | * encoding MIME headers. It subclasses the BASE64EncoderStream 24 | * class. 25 | * 26 | * @author John Mani 27 | */ 28 | 29 | public class BEncoderStream extends BASE64EncoderStream { 30 | 31 | /** 32 | * Create a 'B' encoder that encodes the specified input stream. 33 | * @param out the output stream 34 | */ 35 | public BEncoderStream(OutputStream out) { 36 | super(out, Integer.MAX_VALUE); // MAX_VALUE is 2^31, should 37 | // suffice (!) to indicate that 38 | // CRLFs should not be inserted 39 | } 40 | 41 | /** 42 | * Returns the length of the encoded version of this byte array. 43 | * 44 | * @param b the byte array 45 | * @return the length 46 | */ 47 | public static int encodedLength(byte[] b) { 48 | return ((b.length + 2)/3) * 4; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/jakarta/mail/Header.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.jakarta.mail; 18 | 19 | 20 | /** 21 | * The Header class stores a name/value pair to represent headers. 22 | * 23 | * @author John Mani 24 | */ 25 | 26 | public class Header { 27 | 28 | /** 29 | * The name of the header. 30 | * 31 | * @since JavaMail 1.4 32 | */ 33 | protected String name; 34 | 35 | /** 36 | * The value of the header. 37 | * 38 | * @since JavaMail 1.4 39 | */ 40 | protected String value; 41 | 42 | /** 43 | * Construct a Header object. 44 | * 45 | * @param name name of the header 46 | * @param value value of the header 47 | */ 48 | public Header(String name, String value) { 49 | this.name = name; 50 | this.value = value; 51 | } 52 | 53 | /** 54 | * Returns the name of this header. 55 | * 56 | * @return name of the header 57 | */ 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | /** 63 | * Returns the value of this header. 64 | * 65 | * @return value of the header 66 | */ 67 | public String getValue() { 68 | return value; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/OutlookFieldInformation.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | import org.apache.poi.hsmf.datatypes.MAPIProperty; 4 | import org.apache.poi.poifs.filesystem.DocumentEntry; 5 | 6 | /** 7 | * Convenience class for storing type information about a {@link DocumentEntry}. 8 | */ 9 | public class OutlookFieldInformation { 10 | 11 | /** 12 | * The default value for both the {@link #clazz} and the {@link #type} properties. 13 | */ 14 | public static final String UNKNOWN = "unknown"; 15 | 16 | /** 17 | * The default value for the {@link #mapiType} 18 | */ 19 | public static final int UNKNOWN_MAPITYPE = -1; 20 | 21 | /** 22 | * The class of the {@link DocumentEntry}. 23 | */ 24 | private final String clazz; 25 | 26 | /** 27 | * The type of the {@link DocumentEntry}. 28 | */ 29 | private final String type; 30 | 31 | /** 32 | * The mapi type of the {@link DocumentEntry}. 33 | */ 34 | private final int mapiType; 35 | 36 | /** 37 | * Delegates to {@link #OutlookFieldInformation(String, int)} with values {@value #UNKNOWN}, {@value #UNKNOWN} and {@value #UNKNOWN_MAPITYPE}. 38 | */ 39 | public OutlookFieldInformation() { 40 | this(UNKNOWN, UNKNOWN_MAPITYPE); 41 | } 42 | 43 | /** 44 | * @param clazz The class of the {@link DocumentEntry}. 45 | * @param mapiType The mapiType of the {@link DocumentEntry} (see {@link MAPIProperty}). 46 | */ 47 | public OutlookFieldInformation(final String clazz, final int mapiType) { 48 | this.clazz = clazz; 49 | this.type = UNKNOWN; 50 | this.mapiType = mapiType; 51 | } 52 | 53 | /** 54 | * Bean getter for {@link #clazz}. 55 | */ 56 | public String getClazz() { 57 | return clazz; 58 | } 59 | 60 | /** 61 | * Bean getter for {@link #type}. 62 | */ 63 | public String getType() { 64 | return type; 65 | } 66 | 67 | /** 68 | * Bean getter for {@link #mapiType}. 69 | */ 70 | public int getMapiType() { 71 | return mapiType; 72 | } 73 | } -------------------------------------------------------------------------------- /src/test/java/org/simplejavamail/outlookmessageparser/model/OutlookMessageTest.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.simplejavamail.outlookmessageparser.model.OutlookSmime.OutlookSmimeApplicationSmime; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | public class OutlookMessageTest { 9 | 10 | @Test 11 | public void testSetSmimeNull() { 12 | OutlookMessage msg = new OutlookMessage(); 13 | msg.setSmimeApplicationSmime(null); 14 | assertThat(msg.getSmime()).isNull(); 15 | msg.setSmimeApplicationSmime(""); 16 | assertThat(msg.getSmime()).isNull(); 17 | msg.setSmimeApplicationSmime("moomoo"); 18 | assertThat(msg.getSmime()).isNull(); 19 | } 20 | 21 | @Test 22 | public void testSetSmimeNonNull() { 23 | // application/pkcs7-mime;smime-type=signed-data;name=smime.p7m 24 | testSmime("application/pkcs7-mime", "application/pkcs7-mime", null, null); 25 | testSmime("application/pkcs7-mime;", "application/pkcs7-mime", null, null); 26 | testSmime("application/pkcs7-mime;name=moo", "application/pkcs7-mime", null, "moo"); 27 | testSmime("application/pkcs7-mime;smime-type=signed-data;name=smime.p7m", "application/pkcs7-mime", "signed-data", "smime.p7m"); 28 | testSmime("application/pkcs7-mime;name=smime.p7m;smime-type=signed-data", "application/pkcs7-mime", "signed-data", "smime.p7m"); 29 | testSmime("application/pkcs7-mime;name=smime.p7m;smime-type=signed-data;", "application/pkcs7-mime", "signed-data", "smime.p7m"); 30 | } 31 | 32 | private void testSmime(String smimeHeader, String smimeMime, String smimeType, String smimeName) { 33 | OutlookMessage msg = new OutlookMessage(); 34 | msg.setSmimeApplicationSmime(smimeHeader); 35 | 36 | OutlookSmimeApplicationSmime smime = (OutlookSmimeApplicationSmime) msg.getSmime(); 37 | assertThat(smime.getSmimeMime()).isEqualTo(smimeMime); 38 | assertThat(smime.getSmimeType()).isEqualTo(smimeType); 39 | assertThat(smime.getSmimeName()).isEqualTo(smimeName); 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/java/org/simplejavamail/outlookmessageparser/model/OutlookFileAttachmentTest.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | public class OutlookFileAttachmentTest { 8 | 9 | @Test 10 | public void checkSmimeFilename() { 11 | testSmimeFilenameScenario(null, "image/png", null); 12 | testSmimeFilenameScenario("file", "image/png", "file"); 13 | testSmimeFilenameScenario("file", "multipart/signed", "file"); 14 | testSmimeFilenameScenario(null, "multipart/signed", "smime.p7s"); 15 | testSmimeFilenameScenario(null, "multipart/signed;protocol=\"moomoo\"", null); 16 | testSmimeFilenameScenario("file", "multipart/signed;protocol=\"moomoo\"", "file"); 17 | testSmimeFilenameScenario(null, "multipart/signed;protocol=\"application/pkcs7-signature\"", "smime.p7s"); 18 | testSmimeFilenameScenario("file", "multipart/signed;protocol=\"application/pkcs7-signature\"", "file"); 19 | } 20 | 21 | private void testSmimeFilenameScenario(String filename, String mimeTag, String expectedNewFilename) { 22 | OutlookFileAttachment subject = new OutlookFileAttachment(); 23 | subject.setFilename(filename); 24 | subject.setMimeTag(mimeTag); 25 | 26 | subject.checkSmimeFilename(); 27 | assertThat(subject.getFilename()).isEqualTo(expectedNewFilename); 28 | } 29 | 30 | @Test 31 | public void checkMimeTag() { 32 | testMimeTagScenario("image/png", "image.png", "image/png"); 33 | testMimeTagScenario("image/png", "image.bmp", "image/png"); 34 | testMimeTagScenario("image/png", null, "image/png"); 35 | testMimeTagScenario(null, null, null); 36 | testMimeTagScenario(null, "moo", "application/octet-stream"); 37 | testMimeTagScenario(null, "image.png", "image/png"); 38 | testMimeTagScenario(null, "image.bmp", "image/bmp"); 39 | } 40 | 41 | private void testMimeTagScenario(String mimeTag, String filename, String expectedNewMimeTag) { 42 | OutlookFileAttachment subject = new OutlookFileAttachment(); 43 | subject.setMimeTag(mimeTag); 44 | subject.setFilename(filename); 45 | 46 | subject.checkMimeTag(); 47 | assertThat(subject.getMimeTag()).isEqualTo(expectedNewMimeTag); 48 | } 49 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | github-maven-deploy: github-maven-deploy/github-maven-deploy@1.3.0 5 | 6 | mvn-build-test-command: &mvn-build-test-command 7 | mvn-build-test-command: mvn verify -Dmaven.javadoc.skip=true -Djacoco.skip=true -Dlicense.skip=true 8 | 9 | mvn-deploy-command: &mvn-deploy-command 10 | mvn-deploy-command: | 11 | mvn -s .circleci/maven-release-settings.xml clean deploy -DdeployAtEnd=true -DperformRelease=true -DskipTests -Dspotbugs.skip=true -Denforcer.skip=true -Djacoco.skip=true 12 | mvn license:remove 13 | context: RELEASE_PROFILE_BBOTTEMA 14 | 15 | workflows: 16 | workflow: 17 | jobs: 18 | - github-maven-deploy/build-and-test: 19 | <<: *mvn-build-test-command 20 | filters: 21 | branches: 22 | only: master 23 | 24 | - github-maven-deploy/approve-deploy-patch-version: 25 | type: approval 26 | requires: 27 | - github-maven-deploy/build-and-test 28 | - github-maven-deploy/approve-deploy-minor-version: 29 | type: approval 30 | requires: 31 | - github-maven-deploy/build-and-test 32 | - github-maven-deploy/approve-deploy-major-version: 33 | type: approval 34 | requires: 35 | - github-maven-deploy/build-and-test 36 | - github-maven-deploy/approve-deploy-as-is-version: 37 | type: approval 38 | requires: 39 | - github-maven-deploy/build-and-test 40 | 41 | - github-maven-deploy/deploy-patch-version: 42 | requires: 43 | - github-maven-deploy/approve-deploy-patch-version 44 | <<: *mvn-deploy-command 45 | - github-maven-deploy/deploy-minor-version: 46 | requires: 47 | - github-maven-deploy/approve-deploy-minor-version 48 | <<: *mvn-deploy-command 49 | - github-maven-deploy/deploy-major-version: 50 | requires: 51 | - github-maven-deploy/approve-deploy-major-version 52 | <<: *mvn-deploy-command 53 | - github-maven-deploy/deploy-as-is-version: 54 | requires: 55 | - github-maven-deploy/approve-deploy-as-is-version 56 | <<: *mvn-deploy-command 57 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/jakarta/mail/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.jakarta.mail; 18 | 19 | import java.io.Serializable; 20 | 21 | /** 22 | * This abstract class models the addresses in a message. 23 | * Subclasses provide specific implementations. Subclasses 24 | * will typically be serializable so that (for example) the 25 | * use of Address objects in search terms can be serialized 26 | * along with the search terms. 27 | * 28 | * @author John Mani 29 | * @author Bill Shannon 30 | */ 31 | 32 | public abstract class Address implements Serializable { 33 | 34 | private static final long serialVersionUID = -5822459626751992278L; 35 | 36 | /** 37 | * Return a type string that identifies this address type. 38 | * 39 | * @return address type 40 | * @see org.simplejavamail.jakarta.mail.internet.InternetAddress 41 | */ 42 | public abstract String getType(); 43 | 44 | /** 45 | * Return a String representation of this address object. 46 | * 47 | * @return string representation of this address 48 | */ 49 | @Override 50 | public abstract String toString(); 51 | 52 | /** 53 | * The equality operator. Subclasses should provide an 54 | * implementation of this method that supports value equality 55 | * (do the two Address objects represent the same destination?), 56 | * not object reference equality. A subclass must also provide 57 | * a corresponding implementation of the hashCode 58 | * method that preserves the general contract of 59 | * equals and hashCode - objects that 60 | * compare as equal must have the same hashCode. 61 | * 62 | * @param address Address object 63 | */ 64 | @Override 65 | public abstract boolean equals(Object address); 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/simplejavamail/outlookmessageparser/OutlookMessageParserTest.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.simplejavamail.outlookmessageparser.model.OutlookMessage; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | public class OutlookMessageParserTest { 9 | 10 | private static final String HEADERS = "Date: Sun, 5 Mar 2017 12:11:31 +0100\n" 11 | + "Reply-To: lollypop-replyto \n" 12 | + "To: C.Cane "; 13 | 14 | @Test 15 | public void extractReplyToHeader() { 16 | OutlookMessage msg = new OutlookMessage(); 17 | OutlookMessageParser.extractReplyToHeader(msg, HEADERS); 18 | assertThat(msg.getReplyToName()).isEqualTo("lollypop-replyto"); 19 | assertThat(msg.getReplyToEmail()).isEqualTo("lo.pop.replyto@somemail.com"); 20 | } 21 | 22 | @Test 23 | public void extractReplyToPartialAddressHeader() { 24 | OutlookMessage msg = new OutlookMessage(); 25 | OutlookMessageParser.extractReplyToHeader(msg, "Reply-To: \n"); 26 | assertThat(msg.getReplyToName()).isEqualTo("lo.pop.replyto@somemail.com"); 27 | assertThat(msg.getReplyToEmail()).isEqualTo("lo.pop.replyto@somemail.com"); 28 | } 29 | 30 | @Test 31 | public void extractReplyToPartialNameHeader() { 32 | OutlookMessage msg = new OutlookMessage(); 33 | OutlookMessageParser.extractReplyToHeader(msg, "Reply-To: lo.pop.replyto@somemail.com\n"); 34 | assertThat(msg.getReplyToName()).isEqualTo("lo.pop.replyto@somemail.com"); 35 | assertThat(msg.getReplyToEmail()).isEqualTo("lo.pop.replyto@somemail.com"); 36 | } 37 | 38 | @Test 39 | public void extractReplyToHeaderMissing() { 40 | OutlookMessage msg = new OutlookMessage(); 41 | OutlookMessageParser.extractReplyToHeader(msg, "Date: Sun, 5 Mar 2017 12:11:31 +0100\n" 42 | + "To: C.Cane "); 43 | assertThat(msg.getReplyToName()).isNull(); 44 | assertThat(msg.getReplyToEmail()).isNull(); 45 | } 46 | 47 | @Test 48 | public void extractReplyToHeaderMalformed1() { 49 | OutlookMessage msg = new OutlookMessage(); 50 | OutlookMessageParser.extractReplyToHeader(msg, "Reply-To: tg>"); 59 | assertThat(msg.getReplyToName()).isEqualTo("lo.pop.r"); 60 | assertThat(msg.getReplyToEmail()).isEqualTo("eplyto@somemail.com>tg"); 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/OutlookSmime.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | import java.util.Objects; 4 | 5 | // https://tools.ietf.org/html/rfc5751#page-32 (Identifying an S/MIME Message) 6 | public abstract class OutlookSmime { 7 | 8 | public static class OutlookSmimeApplicationSmime extends OutlookSmime { 9 | private final String smimeMime; 10 | private final String smimeType; 11 | private final String smimeName; 12 | 13 | public OutlookSmimeApplicationSmime(String smimeMime, String smimeType, String smimeName) { 14 | this.smimeMime = smimeMime; 15 | this.smimeType = smimeType; 16 | this.smimeName = smimeName; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) return true; 22 | if (o == null || getClass() != o.getClass()) return false; 23 | OutlookSmimeApplicationSmime that = (OutlookSmimeApplicationSmime) o; 24 | return Objects.equals(smimeMime, that.smimeMime) && 25 | Objects.equals(smimeType, that.smimeType) && 26 | Objects.equals(smimeName, that.smimeName); 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return Objects.hash(smimeMime, smimeType, smimeName); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | final StringBuilder sb = new StringBuilder("OutlookSmimeApplicationSmime{"); 37 | sb.append("smimeMime='").append(smimeMime).append('\''); 38 | sb.append(", smimeType='").append(smimeType).append('\''); 39 | sb.append(", smimeName='").append(smimeName).append('\''); 40 | sb.append('}'); 41 | return sb.toString(); 42 | } 43 | 44 | public String getSmimeMime() { return smimeMime; } 45 | public String getSmimeType() { return smimeType; } 46 | public String getSmimeName() { return smimeName; } 47 | } 48 | 49 | public static class OutlookSmimeMultipartSigned extends OutlookSmime { 50 | private final String smimeMime; 51 | private final String smimeProtocol; 52 | private final String smimeMicalg; 53 | 54 | public OutlookSmimeMultipartSigned(String smimeMime, String smimeProtocol, String smimeMicalg) { 55 | this.smimeMime = smimeMime; 56 | this.smimeProtocol = smimeProtocol; 57 | this.smimeMicalg = smimeMicalg; 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | OutlookSmimeMultipartSigned that = (OutlookSmimeMultipartSigned) o; 65 | return Objects.equals(smimeMime, that.smimeMime) && 66 | Objects.equals(smimeProtocol, that.smimeProtocol) && 67 | Objects.equals(smimeMicalg, that.smimeMicalg); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return Objects.hash(smimeMime, smimeProtocol, smimeMicalg); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | final StringBuilder sb = new StringBuilder("OutlookSmimeMultipartSigned{"); 78 | sb.append("smimeMime='").append(smimeMime).append('\''); 79 | sb.append(", smimeProtocol='").append(smimeProtocol).append('\''); 80 | sb.append(", smimeMicalg='").append(smimeMicalg).append('\''); 81 | sb.append('}'); 82 | return sb.toString(); 83 | } 84 | 85 | public String getSmimeMime() { return smimeMime; } 86 | public String getSmimeProtocol() { return smimeProtocol; } 87 | public String getSmimeMicalg() { return smimeMicalg; } 88 | } 89 | 90 | public static class OutlookSmimeApplicationOctetStream extends OutlookSmime { 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/jakarta/mail/internet/AddressException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.jakarta.mail.internet; 18 | 19 | /** 20 | * The exception thrown when a wrongly formatted address is encountered. 21 | * 22 | * @author Bill Shannon 23 | * @author Max Spivak 24 | */ 25 | 26 | public class AddressException extends ParseException { 27 | /** 28 | * The string being parsed. 29 | * 30 | * @serial 31 | */ 32 | protected String ref = null; 33 | 34 | /** 35 | * The index in the string where the error occurred, or -1 if not known. 36 | * 37 | * @serial 38 | */ 39 | protected int pos = -1; 40 | 41 | private static final long serialVersionUID = 9134583443539323120L; 42 | 43 | /** 44 | * Constructs an AddressException with no detail message. 45 | */ 46 | public AddressException() { 47 | super(); 48 | } 49 | 50 | /** 51 | * Constructs an AddressException with the specified detail message. 52 | * @param s the detail message 53 | */ 54 | public AddressException(String s) { 55 | super(s); 56 | } 57 | 58 | /** 59 | * Constructs an AddressException with the specified detail message 60 | * and reference info. 61 | * 62 | * @param s the detail message 63 | * @param ref the string being parsed 64 | */ 65 | public AddressException(String s, String ref) { 66 | super(s); 67 | this.ref = ref; 68 | } 69 | 70 | /** 71 | * Constructs an AddressException with the specified detail message 72 | * and reference info. 73 | * 74 | * @param s the detail message 75 | * @param ref the string being parsed 76 | * @param pos the position of the error 77 | */ 78 | public AddressException(String s, String ref, int pos) { 79 | super(s); 80 | this.ref = ref; 81 | this.pos = pos; 82 | } 83 | 84 | /** 85 | * Get the string that was being parsed when the error was detected 86 | * (null if not relevant). 87 | * 88 | * @return the string that was being parsed 89 | */ 90 | public String getRef() { 91 | return ref; 92 | } 93 | 94 | /** 95 | * Get the position with the reference string where the error was 96 | * detected (-1 if not relevant). 97 | * 98 | * @return the position within the string of the error 99 | */ 100 | public int getPos() { 101 | return pos; 102 | } 103 | 104 | @Override 105 | public String toString() { 106 | String s = super.toString(); 107 | if (ref == null) 108 | return s; 109 | s += " in string ``" + ref + "''"; 110 | if (pos < 0) 111 | return s; 112 | return s + " at position " + pos; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/com/sun/mail/util/QEncoderStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.com.sun.mail.util; 18 | 19 | import java.io.*; 20 | 21 | /** 22 | * This class implements a Q Encoder as defined by RFC 2047 for 23 | * encoding MIME headers. It subclasses the QPEncoderStream class. 24 | * 25 | * @author John Mani 26 | */ 27 | 28 | public class QEncoderStream extends QPEncoderStream { 29 | 30 | private String specials; 31 | private static String WORD_SPECIALS = "=_?\"#$%&'(),.:;<>@[\\]^`{|}~"; 32 | private static String TEXT_SPECIALS = "=_?"; 33 | 34 | /** 35 | * Create a Q encoder that encodes the specified input stream 36 | * @param out the output stream 37 | * @param encodingWord true if we are Q-encoding a word within a 38 | * phrase. 39 | */ 40 | public QEncoderStream(OutputStream out, boolean encodingWord) { 41 | super(out, Integer.MAX_VALUE); // MAX_VALUE is 2^31, should 42 | // suffice (!) to indicate that 43 | // CRLFs should not be inserted 44 | // when encoding rfc822 headers 45 | 46 | // a RFC822 "word" token has more restrictions than a 47 | // RFC822 "text" token. 48 | specials = encodingWord ? WORD_SPECIALS : TEXT_SPECIALS; 49 | } 50 | 51 | /** 52 | * Encodes the specified byte to this output stream. 53 | * @param c the byte. 54 | * @exception IOException if an I/O error occurs. 55 | */ 56 | @Override 57 | public void write(int c) throws IOException { 58 | c = c & 0xff; // Turn off the MSB. 59 | if (c == ' ') 60 | output('_', false); 61 | else if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0) 62 | // Encoding required. 63 | output(c, true); 64 | else // No encoding required 65 | output(c, false); 66 | } 67 | 68 | /** 69 | * Returns the length of the encoded version of this byte array. 70 | * 71 | * @param b the byte array 72 | * @param encodingWord true if encoding words, false if encoding text 73 | * @return the length 74 | */ 75 | public static int encodedLength(byte[] b, boolean encodingWord) { 76 | int len = 0; 77 | String specials = encodingWord ? WORD_SPECIALS: TEXT_SPECIALS; 78 | for (int i = 0; i < b.length; i++) { 79 | int c = b[i] & 0xff; // Mask off MSB 80 | if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0) 81 | // needs encoding 82 | len += 3; // Q-encoding is 1 -> 3 conversion 83 | else 84 | len++; 85 | } 86 | return len; 87 | } 88 | 89 | /**** begin TEST program *** 90 | public static void main(String argv[]) throws Exception { 91 | FileInputStream infile = new FileInputStream(argv[0]); 92 | QEncoderStream encoder = new QEncoderStream(System.out); 93 | int c; 94 | 95 | while ((c = infile.read()) != -1) 96 | encoder.write(c); 97 | encoder.close(); 98 | } 99 | *** end TEST program ***/ 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/com/sun/mail/util/PropUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.com.sun.mail.util; 18 | 19 | import java.util.*; 20 | 21 | /** 22 | * Utilities to make it easier to get property values. 23 | * Properties can be strings or type-specific value objects. 24 | * 25 | * @author Bill Shannon 26 | */ 27 | public class PropUtil { 28 | 29 | // No one should instantiate this class. 30 | private PropUtil() { 31 | } 32 | 33 | /** 34 | * Get an integer valued property. 35 | * 36 | * @param props the properties 37 | * @param name the property name 38 | * @param def default value if property not found 39 | * @return the property value 40 | */ 41 | public static int getIntProperty(Properties props, String name, int def) { 42 | return getInt(getProp(props, name), def); 43 | } 44 | 45 | /** 46 | * Get a boolean valued property. 47 | * 48 | * @param props the properties 49 | * @param name the property name 50 | * @param def default value if property not found 51 | * @return the property value 52 | */ 53 | public static boolean getBooleanProperty(Properties props, 54 | String name, boolean def) { 55 | return getBoolean(getProp(props, name), def); 56 | } 57 | 58 | /** 59 | * Get a boolean valued System property. 60 | * 61 | * @param name the property name 62 | * @param def default value if property not found 63 | * @return the property value 64 | */ 65 | public static boolean getBooleanSystemProperty(String name, boolean def) { 66 | try { 67 | return getBoolean(getProp(System.getProperties(), name), def); 68 | } catch (SecurityException sex) { 69 | // fall through... 70 | } 71 | 72 | /* 73 | * If we can't get the entire System Properties object because 74 | * of a SecurityException, just ask for the specific property. 75 | */ 76 | try { 77 | String value = System.getProperty(name); 78 | if (value == null) 79 | return def; 80 | if (def) 81 | return !value.equalsIgnoreCase("false"); 82 | else 83 | return value.equalsIgnoreCase("true"); 84 | } catch (SecurityException sex) { 85 | return def; 86 | } 87 | } 88 | 89 | /** 90 | * Get the value of the specified property. 91 | * If the "get" method returns null, use the getProperty method, 92 | * which might cascade to a default Properties object. 93 | */ 94 | private static Object getProp(Properties props, String name) { 95 | Object val = props.get(name); 96 | if (val != null) 97 | return val; 98 | else 99 | return props.getProperty(name); 100 | } 101 | 102 | /** 103 | * Interpret the value object as an integer, 104 | * returning def if unable. 105 | */ 106 | private static int getInt(Object value, int def) { 107 | if (value == null) 108 | return def; 109 | if (value instanceof String) { 110 | try { 111 | String s = (String)value; 112 | if (s.startsWith("0x")) 113 | return Integer.parseInt(s.substring(2), 16); 114 | else 115 | return Integer.parseInt(s); 116 | } catch (NumberFormatException nfex) { } 117 | } 118 | if (value instanceof Integer) 119 | return ((Integer)value).intValue(); 120 | return def; 121 | } 122 | 123 | /** 124 | * Interpret the value object as a boolean, 125 | * returning def if unable. 126 | */ 127 | private static boolean getBoolean(Object value, boolean def) { 128 | if (value == null) 129 | return def; 130 | if (value instanceof String) { 131 | /* 132 | * If the default is true, only "false" turns it off. 133 | * If the default is false, only "true" turns it on. 134 | */ 135 | if (def) 136 | return !((String)value).equalsIgnoreCase("false"); 137 | else 138 | return ((String)value).equalsIgnoreCase("true"); 139 | } 140 | if (value instanceof Boolean) 141 | return ((Boolean)value).booleanValue(); 142 | return def; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/jakarta/mail/MessagingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.jakarta.mail; 18 | 19 | import java.lang.*; 20 | 21 | /** 22 | * The base class for all exceptions thrown by the Messaging classes 23 | * 24 | * @author John Mani 25 | * @author Bill Shannon 26 | */ 27 | 28 | public class MessagingException extends Exception { 29 | 30 | /** 31 | * The next exception in the chain. 32 | * 33 | * @serial 34 | */ 35 | private Exception next; 36 | 37 | private static final long serialVersionUID = -7569192289819959253L; 38 | 39 | /** 40 | * Constructs a MessagingException with no detail message. 41 | */ 42 | public MessagingException() { 43 | super(); 44 | initCause(null); // prevent anyone else from setting it 45 | } 46 | 47 | /** 48 | * Constructs a MessagingException with the specified detail message. 49 | * 50 | * @param s the detail message 51 | */ 52 | public MessagingException(String s) { 53 | super(s); 54 | initCause(null); // prevent anyone else from setting it 55 | } 56 | 57 | /** 58 | * Constructs a MessagingException with the specified 59 | * Exception and detail message. The specified exception is chained 60 | * to this exception. 61 | * 62 | * @param s the detail message 63 | * @param e the embedded exception 64 | * @see #getNextException 65 | * @see #setNextException 66 | * @see #getCause 67 | */ 68 | public MessagingException(String s, Exception e) { 69 | super(s); 70 | next = e; 71 | initCause(null); // prevent anyone else from setting it 72 | } 73 | 74 | /** 75 | * Get the next exception chained to this one. If the 76 | * next exception is a MessagingException, the chain 77 | * may extend further. 78 | * 79 | * @return next Exception, null if none. 80 | */ 81 | public synchronized Exception getNextException() { 82 | return next; 83 | } 84 | 85 | /** 86 | * Overrides the getCause method of Throwable 87 | * to return the next exception in the chain of nested exceptions. 88 | * 89 | * @return next Exception, null if none. 90 | */ 91 | @Override 92 | public synchronized Throwable getCause() { 93 | return next; 94 | } 95 | 96 | /** 97 | * Add an exception to the end of the chain. If the end 98 | * is not a MessagingException, this 99 | * exception cannot be added to the end. 100 | * 101 | * @param ex the new end of the Exception chain 102 | * @return true if this Exception 103 | * was added, false otherwise. 104 | */ 105 | public synchronized boolean setNextException(Exception ex) { 106 | Exception theEnd = this; 107 | while (theEnd instanceof MessagingException && 108 | ((MessagingException)theEnd).next != null) { 109 | theEnd = ((MessagingException)theEnd).next; 110 | } 111 | // If the end is a MessagingException, we can add this 112 | // exception to the chain. 113 | if (theEnd instanceof MessagingException) { 114 | ((MessagingException)theEnd).next = ex; 115 | return true; 116 | } else 117 | return false; 118 | } 119 | 120 | /** 121 | * Override toString method to provide information on 122 | * nested exceptions. 123 | */ 124 | @Override 125 | public synchronized String toString() { 126 | String s = super.toString(); 127 | Exception n = next; 128 | if (n == null) 129 | return s; 130 | StringBuilder sb = new StringBuilder(s == null ? "" : s); 131 | while (n != null) { 132 | sb.append(";\n nested exception is:\n\t"); 133 | if (n instanceof MessagingException) { 134 | MessagingException mex = (MessagingException)n; 135 | sb.append(mex.superToString()); 136 | n = mex.next; 137 | } else { 138 | sb.append(n.toString()); 139 | n = null; 140 | } 141 | } 142 | return sb.toString(); 143 | } 144 | 145 | /** 146 | * Return the "toString" information for this exception, 147 | * without any information on nested exceptions. 148 | */ 149 | private final String superToString() { 150 | return super.toString(); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /properties-list1.txt: -------------------------------------------------------------------------------- 1 | This first partial list explains some of the properties we recognize or ignore 2 | 3 | (source: https://github.com/mvz/email-outlook-message-perl/blob/master/lib/Email/Outlook/Message/Base.pm) 4 | 5 | { 6 | # Envelope properties 7 | '0002' => "Alternate Recipient Allowed", 8 | '000B' => "Conversation Key", 9 | '0017' => "Importance", #TODO: Use this. 10 | '001A' => "Message Class", 11 | '0023' => "Originator Delivery Report Requested", 12 | '0026' => "Priority", #TODO: Use this. 13 | '0029' => "Read Receipt Requested", #TODO: Use this. 14 | '0036' => "Sensitivity", # As assessed by the Sender 15 | '003B' => "Sent Representing Search Key", 16 | '003D' => "Subject Prefix", 17 | '003F' => "Received By EntryId", 18 | '0040' => "Received By Name", 19 | # TODO: These two fields are part of the Sender field. 20 | '0041' => "Sent Representing EntryId", 21 | '0042' => "Sent Representing Name", 22 | '0043' => "Received Representing EntryId", 23 | '0044' => "Received Representing Name", 24 | '0046' => "Read Receipt EntryId", 25 | '0051' => "Received By Search Key", 26 | '0052' => "Received Representing Search Key", 27 | '0053' => "Read Receipt Search Key", 28 | # TODO: These two fields are part of the Sender field. 29 | '0064' => "Sent Representing Address Type", 30 | '0065' => "Sent Representing Email Address", 31 | '0070' => "Conversation Topic", 32 | '0071' => "Conversation Index", 33 | '0075' => "Received By Address Type", 34 | '0076' => "Received By Email Address", 35 | '0077' => "Received Representing Address Type", 36 | '0078' => "Received Representing Email Address", 37 | '007F' => "TNEF Correlation Key", 38 | # Recipient properties 39 | '0C15' => "Recipient Type", 40 | # Sender properties 41 | '0C19' => "Sender Entry Id", 42 | '0C1D' => "Sender Search Key", 43 | '0C1E' => "Sender Address Type", 44 | # Non-transmittable properties 45 | '0E02' => "Display Bcc", 46 | '0E06' => "Message Delivery Time", 47 | '0E07' => "Message Flags", 48 | '0E0A' => "Sent Mail EntryId", 49 | '0E0F' => "Responsibility", 50 | '0E1B' => "Has Attachments", 51 | '0E1D' => "Normalized Subject", 52 | '0E1F' => "RTF In Sync", 53 | '0E20' => "Attachment Size", 54 | '0E21' => "Attachment Number", 55 | '0E23' => "Internet Article Number", 56 | '0E27' => "Security Descriptor", 57 | '0E79' => "Trust Sender", 58 | '0FF4' => "Access", 59 | '0FF6' => "Instance Key", 60 | '0FF7' => "Access Level", 61 | '0FF9' => "Record Key", 62 | '0FFE' => "Object Type", 63 | '0FFF' => "EntryId", 64 | # Content properties 65 | '1006' => "RTF Sync Body CRC", 66 | '1007' => "RTF Sync Body Count", 67 | '1008' => "RTF Sync Body Tag", 68 | '1010' => "RTF Sync Prefix Count", 69 | '1011' => "RTF Sync Trailing Count", 70 | '1046' => "Original Message ID", 71 | '1080' => "Icon Index", 72 | '1081' => "Last Verb Executed", 73 | '1082' => "Last Verb Execution Time", 74 | '10F3' => "URL Component Name", 75 | '10F4' => "Attribute Hidden", 76 | '10F5' => "Attribute System", 77 | '10F6' => "Attribute Read Only", 78 | # 'Common property' 79 | '3000' => "Row Id", 80 | '3001' => "Display Name", 81 | '3002' => "Address Type", 82 | '3007' => "Creation Time", 83 | '3008' => "Last Modification Time", 84 | '300B' => "Search Key", 85 | # Message store info 86 | '340D' => "Store Support Mask", 87 | '3414' => "Message Store Provider", 88 | # Attachment properties 89 | '3702' => "Attachment Encoding", 90 | '3703' => "Attachment Extension", 91 | # TODO: Use the following to distinguish between nested msg and other OLE 92 | # stores. 93 | '3705' => "Attachment Method", 94 | '3709' => "Attachment Rendering", # Icon as WMF 95 | '370A' => "Tag identifying application that supplied the attachment", 96 | '370B' => "Attachment Rendering Position", 97 | '3713' => "Attachment Content Location", #TODO: Use this? 98 | # 3900 -- 39FF: 'Address book' 99 | '3900' => "Address Book Display Type", 100 | '39FF' => "Address Book 7 Bit Display Name", 101 | # Mail User Object 102 | '3A00' => "Account", 103 | '3A20' => "Transmittable Display Name", 104 | '3A40' => "Send Rich Info", 105 | '3FDE' => "Internet Code Page", # TODO: Perhaps use this. 106 | # 'Display table properties' 107 | '3FF8' => "Creator Name", 108 | '3FF9' => "Creator EntryId", 109 | '3FFA' => "Last Modifier Name", 110 | '3FFB' => "Last Modifier EntryId", 111 | '3FFD' => "Message Code Page", 112 | # 'Transport-defined envelope property' 113 | '4019' => "Sender Flags", 114 | '401A' => "Sent Representing Flags", 115 | '401B' => "Received By Flags", 116 | '401C' => "Received Representing Flags", 117 | '4029' => "Read Receipt Address Type", 118 | '402A' => "Read Receipt Email Address", 119 | '402B' => "Read Receipt Name", 120 | '5FF6' => "Recipient Display Name", 121 | '5FF7' => "Recipient EntryId", 122 | '5FFD' => "Recipient Flags", 123 | '5FFF' => "Recipient Track Status", 124 | # 'Provider-defined internal non-transmittable property' 125 | '664A' => "Has Named Properties", 126 | '6740' => "Sent Mail Server EntryId", -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/OutlookFileAttachment.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | /** 4 | * Implementation of the {@link OutlookAttachment} interface that represents a file attachment. It contains some useful information (as long as it is available 5 | * in the .msg file) like the attachment name, its size, etc. 6 | */ 7 | public class OutlookFileAttachment implements OutlookAttachment { 8 | 9 | /** 10 | * The (by Outlook) shortened filename of the attachment. 11 | */ 12 | private String filename; 13 | /** 14 | * The full filename of the attachment. 15 | */ 16 | private String longFilename; 17 | /** 18 | * Mime type of the attachment 19 | */ 20 | private String mimeTag; 21 | /** 22 | * CID of the attachment 23 | */ 24 | private String contentId; 25 | /** 26 | * The extension of the attachment (may not be set). 27 | */ 28 | private String extension; 29 | /** 30 | * The attachment itself as a byte array. 31 | */ 32 | private byte[] data; 33 | /** 34 | * The size of the attachment. 35 | */ 36 | private long size = -1; 37 | 38 | /** 39 | * Sets the property specified by the name parameter. Unknown names are ignored. 40 | */ 41 | public void setProperty(final OutlookMessageProperty msgProp) { 42 | final String name = msgProp.getClazz(); 43 | final Object value = msgProp.getData(); 44 | 45 | if (name != null && value != null) { 46 | switch (name) { 47 | case "3701": 48 | setSize(msgProp.getSize()); 49 | setData((byte[]) value); 50 | break; 51 | case "3704": 52 | setFilename((String) value); 53 | break; 54 | case "3707": 55 | setLongFilename((String) value); 56 | break; 57 | case "370e": 58 | setMimeTag((String) value); 59 | break; 60 | case "3703": 61 | setExtension((String) value); 62 | break; 63 | case "3712": 64 | setContentId((String) value); 65 | break; 66 | default: 67 | // property to ignore, for full list see properties-list.txt 68 | } 69 | } 70 | } 71 | 72 | public void checkSmimeFilename() { 73 | if (this.filename == null && this.mimeTag != null) { 74 | if (this.mimeTag.contains("multipart/signed")) { 75 | if (!this.mimeTag.contains("protocol") || this.mimeTag.contains("protocol=\"application/pkcs7-signature\"")) { 76 | this.filename = "smime.p7s"; 77 | } 78 | } 79 | } 80 | } 81 | 82 | public void checkMimeTag() { 83 | if (this.mimeTag == null || this.mimeTag.length() == 0) { 84 | if (this.filename != null && this.filename.length() > 0) { 85 | this.mimeTag = MimeType.getContentType(this.filename); 86 | } else if (this.longFilename != null && this.longFilename.length() > 0) { 87 | this.mimeTag = MimeType.getContentType(this.longFilename); 88 | } 89 | } 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return (longFilename != null) ? longFilename : filename; 95 | } 96 | 97 | /** 98 | * Bean getter for {@link #contentId}. 99 | */ 100 | @SuppressWarnings("ElementOnlyUsedFromTestCode") 101 | public String getContentId() { 102 | return contentId; 103 | } 104 | 105 | /** 106 | * Bean setter for {@link #contentId}. 107 | */ 108 | void setContentId(final String contentId) { 109 | this.contentId = contentId; 110 | } 111 | 112 | /** 113 | * Bean getter for {@link #extension}. 114 | */ 115 | @SuppressWarnings("ElementOnlyUsedFromTestCode") 116 | public String getExtension() { 117 | return extension; 118 | } 119 | 120 | /** 121 | * Bean setter for {@link #extension}. 122 | */ 123 | void setExtension(final String extension) { 124 | this.extension = extension; 125 | } 126 | 127 | /** 128 | * Bean getter for {@link #filename}. 129 | */ 130 | @SuppressWarnings("ElementOnlyUsedFromTestCode") 131 | public String getFilename() { 132 | return filename; 133 | } 134 | 135 | /** 136 | * Bean setter for {@link #filename}. 137 | */ 138 | void setFilename(final String filename) { 139 | this.filename = filename; 140 | } 141 | 142 | /** 143 | * Bean getter for {@link #longFilename}. 144 | */ 145 | public String getLongFilename() { 146 | return longFilename; 147 | } 148 | 149 | /** 150 | * Bean setter for {@link #longFilename}. 151 | */ 152 | void setLongFilename(final String longFilename) { 153 | this.longFilename = longFilename; 154 | } 155 | 156 | /** 157 | * Bean getter for {@link #mimeTag}. 158 | */ 159 | @SuppressWarnings("ElementOnlyUsedFromTestCode") 160 | public String getMimeTag() { 161 | return mimeTag; 162 | } 163 | 164 | /** 165 | * Bean setter for {@link #mimeTag}. 166 | */ 167 | void setMimeTag(final String mimeTag) { 168 | this.mimeTag = mimeTag; 169 | } 170 | 171 | /** 172 | * Bean getter for {@link #data}. 173 | */ 174 | @SuppressWarnings("ElementOnlyUsedFromTestCode") 175 | public byte[] getData() { 176 | return data.clone(); 177 | } 178 | 179 | /** 180 | * Bean setter for {@link #data}. 181 | */ 182 | void setData(final byte[] data) { 183 | this.data = data; 184 | } 185 | 186 | /** 187 | * Bean getter for {@link #size}. 188 | */ 189 | public long getSize() { 190 | return size; 191 | } 192 | 193 | /** 194 | * Bean setter for {@link #size}. 195 | */ 196 | void setSize(final long size) { 197 | this.size = size; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /RELEASE.txt: -------------------------------------------------------------------------------- 1 | v1.14.0 - v1.14.1 2 | 3 | - 1.14.1 (08-06-2024): #64: [Bug] Parsing lists to HTML has double bullet points 4 | - 1.14.0 (25-05-2024): #80: RTF converted to HTML doesn't always detect charset properly 5 | 6 | 7 | v1.13.0 - v1.13.4 8 | 9 | - 1.13.4 (04-May-2024): bumped apache poi to 5.2.5 and managed commons-io to 2.16.1 10 | - 1.13.3 (04-May-2024): bumped angus-activation from 2.0.2 to 2.0.3 11 | - 1.13.2 (05-April-2024): #73: Don't overwrite existing address, but do retain X500 address if available 12 | - 1.13.1 (04-April-2024): #73: Further improve X500 addresses detection 13 | - 1.13.0 (18-January-2024): #71: Update to latest Jakarta+Angus dependencies 14 | 15 | 16 | v1.12.0 (10-December-2023) 17 | 18 | - #70: [Enhancement] ignore recipients with null-address 19 | 20 | 21 | v1.11.0 - v1.11.1 22 | 23 | - 1.11.1 (08-December-2023): #69: Enhancement: instead of ignoring them completely, only ignore for embedded images 24 | - 1.11.0 (08-December-2023): #69: Enhancement: ignore attachment with missing content 25 | 26 | 27 | v1.10.0 - v1.10.2 28 | 29 | - 1.10.2 (03-December-2023): #68: Improved heuristics for X500 Names 30 | - 1.10.1 (24-October-2023): #67: Fixed "Adding possibility to parse X500 Names" 31 | - 1.10.0 (24-October-2023): #67: Adding possibility to parse X500 Names (don't use this version) 32 | 33 | 34 | v1.9.0 - v1.9.7 35 | 36 | - 1.9.6 (18-July-2022): #57: Same, but now with Collection values to support duplicate headers 37 | - 1.9.5 (18-July-2022): #57: Headers should be more accessible, rather than just a big string of text 38 | - 1.9.x - a bunch of dependency fixes and tries apparently, my release train was not so smooth here, sorry 39 | - 1.9.0 (13-May-2021): #55: CVE issue: Update Apache POI and POI Scratchpad 40 | 41 | 42 | v1.8.0 - v1.8.1 43 | 44 | - v1.8.1 (31-January-2022): #41: OutlookMessage.getPropertyValue() should be public 45 | - v1.8.0 (31-January-2022): #52: Adjust dependencies and make Java 9+ friendly 46 | - v1.8.0 (31-January-2022): #45: Bump commons-io from 2.6 to 2.7 47 | 48 | 49 | v1.7.10 - v1.7.13 (17-November-2021) 50 | 51 | - #49: bugfix solved by improved charset handling 52 | - #46: bugfix Rare NPE case of producing empty nested outlook attachment when there should be no attachments 53 | - #43: bugfix getFromEmailFromHeaders cannot handle "quoted-name-with@at-sign" 54 | - some minor code improvements 55 | 56 | 57 | v1.7.9 (10-October-2020) 58 | 59 | - #28/#36: bugfix NumberFormatException on parsing .msg files 60 | 61 | 62 | v1.7.8 (4-August-2020) 63 | 64 | - #35: Clarify permission to publish project using Apache v2 license 65 | 66 | 67 | v1.7.0 - v1.7.7 (9-January-2020 - 17-July-2020) 68 | 69 | - v1.7.7 - #34: Wrong encoding for bodyHTML 70 | - v1.7.5 - #31: Bugfix for attachments with special characters in the name 71 | - v1.7.4 - #27: Same as 1.7.3, but now also for chinese senders 72 | - v1.7.3 - #27: When from name/address are not available (unsent emails), these fields are filled with binary garbage 73 | - v1.7.2 - #26: To email address is not handled properly when name is omitted 74 | - v1.7.1 - #25: NPE on ClientSubmitTime when original message has not been sent yet 75 | - v1.7.1 - #23: Bug: __nameid_ directory should not be parsed (and causing invalid HTML body) 76 | - v1.7.0 - #18: Upgrade Apache POI 3.9 -> 4.x 77 | 78 | Note: Apache POI requires minimum Java 8 79 | 80 | 81 | v1.6.0 (8-January-2020) 82 | 83 | - #21: Multiple TO recipients are not handles properly 84 | 85 | 86 | v1.5.0 (18-December-2019) 87 | 88 | - #20: CC and BCC recipients are not parsed properly 89 | - #19: Use real Outlook ContentId Attribute to resolve CID Attachments 90 | 91 | 92 | v1.4.1 (22-October-2019) 93 | 94 | - #17: Fixed encoding error for UTF-8's Windows legacy name (cp)65001 95 | 96 | 97 | v1.4.0 (13-October-2019) 98 | 99 | - #9: Replaced the RFC to HTML converter with a brand new RFC-compliant convert! (thanks to @fadeyev!) 100 | 101 | 102 | v1.3.0 (4-October-2019) 103 | 104 | - #14: Dependency problem with Java9+, missing Jakarta Activation Framework 105 | - #13: HTML start tags with extra space not handled correctly 106 | - #11: SimpleRTF2HTMLConverter inserts too many
tags 107 | - #10: Embedded images with DOS-like names are classified as attachments 108 | - #9: SimpleRTF2HTMLConverter removes some valid tags during conversion 109 | 110 | 111 | v1.2.1 (12-May-2019) 112 | 113 | - Ignore non S/MIME related content types when extracting S/MIME metadata 114 | - Added toString and equals methods to the S/MIME data classes 115 | 116 | 117 | v1.1.21 (4-May-2019) 118 | 119 | - Upgraded mediatype recognition based on file extension for incomplete attachments 120 | - Added / improved support for public S/MIME meta data 121 | 122 | 123 | 1.1.20 (14-April-2019) 124 | 125 | - #7: Fix missing S/MIME header details that are needed to determine the type of S/MIME application 126 | 127 | 128 | 1.1.19 (10-April-2019) 129 | 130 | - Log rtf compression error, but otherwise ignore it and keep going and extract what we can. 131 | 132 | 133 | 1.1.18 (5-April-2019) 134 | 135 | - #6: Missing mimeTag for attachments should be guessed based on file extension 136 | 137 | 138 | 1.1.17 (19-August-2018) 139 | 140 | - implemented robust support for character sets / code pages in RTF to HTML conversion (fixes chinese support #3) 141 | - fixed bug where too much text was cleaned up as part of superfluous RTF cleanup step when converting to HTML 142 | - Performance boost in the RTF -> HTML converter 143 | 144 | 145 | 1.1.16 (~28-Februari-2017) 146 | 147 | - First Maven deployment, continuing version number from 1.1.15 of msgparser (https://github.com/bbottema/msgparser) 148 | 149 | 150 | 1.16 151 | - Added support for replyTo name and address 152 | - cleaned up code (1st wave) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | com.github.bbottema 8 | standard-project-parent 9 | 1.0.45 10 | 11 | 12 | org.simplejavamail 13 | outlook-message-parser 14 | jar 15 | Outlook Message Parser 16 | 1.14.1 17 | A Java parser for Outlook messages (.msg files) 18 | https://github.com/bbottema/outlook-message-parser 19 | 2017 20 | 21 | 22 | scm:git:git://github.com/bbottema/outlook-message-parser.git 23 | scm:git:git@github.com:bbottema/outlook-message-parser.git 24 | https://github.com/bbottema/outlook-message-parser 25 | 26 | 27 | 28 | GitHub Issues 29 | https://github.com/bbottema/outlook-message-parser/issues 30 | 31 | 32 | 33 | false 34 | Benny Bottema 35 | benny@bennybottema.com 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.apache.poi 43 | poi 44 | 5.2.5 45 | 46 | 47 | org.apache.logging.log4j 48 | * 49 | 50 | 51 | 52 | 53 | org.apache.poi 54 | poi-scratchpad 55 | 5.2.5 56 | 57 | 58 | commons-codec 59 | commons-codec 60 | 61 | 62 | 63 | 64 | com.github.bbottema 65 | rtf-to-html 66 | 1.1.1 67 | 68 | 69 | org.eclipse.angus 70 | angus-activation 71 | 2.0.2 72 | 73 | 74 | 75 | 76 | 77 | jakarta.annotation 78 | jakarta.annotation-api 79 | 1.3.5 80 | test 81 | 82 | 83 | 84 | 85 | 86 | 87 | commons-io 88 | commons-io 89 | 2.16.1 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | org.assertj 98 | assertj-assertions-generator-maven-plugin 99 | 2.1.0 100 | 101 | 102 | 103 | generate-assertions 104 | 105 | 106 | 107 | 108 | 109 | org.simplejavamail.outlookmessageparser.model.OutlookMessage 110 | 111 | 112 | 113 | 114 | com.mycila 115 | license-maven-plugin 116 | 3.0 117 | 118 |
${license.type}
119 | 120 | ${license.owner.name} 121 | ${license.owner.email} 122 | 123 | 124 | SLASHSTAR_STYLE 125 | 126 | 127 | src/main/java/**/*.java 128 | 129 | 130 | **/jakarta/mail/** 131 | **/com/sun/mail/** 132 | 133 |
134 | 135 | remove-licence-boilerplate-on-cleancleanremove 136 | add-licence-boilerplate-on-compileprocess-sourcesformat 137 | remove-licence-boilerplate-after-packagepackageremove 138 | 139 |
140 | 141 | org.apache.maven.plugins 142 | maven-enforcer-plugin 143 | 3.0.0-M3 144 | 145 | 146 | enforce-versions 147 | 148 | enforce 149 | 150 | 151 | 152 | 153 | 154 | true 155 | 156 | 157 | 158 | 159 | 160 | 161 |
162 |
163 | 164 |
-------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/outlookmessageparser/model/OutlookRecipient.java: -------------------------------------------------------------------------------- 1 | package org.simplejavamail.outlookmessageparser.model; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import java.util.Set; 9 | import java.util.TreeMap; 10 | 11 | import static java.lang.Integer.parseInt; 12 | 13 | /** 14 | * This class represents a recipient's entry of the parsed .msg file. It provides informations like the email address and the display name. 15 | */ 16 | public class OutlookRecipient { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(OutlookRecipient.class); 19 | 20 | private static final String X500_ADDRESS_PATTERN = "/o=[^/]+/ou=[^/]+(?:/cn=[^/]+)*"; 21 | 22 | /** 23 | * Contains all properties that are not covered by the special properties. 24 | */ 25 | private final Map properties = new TreeMap<>(); 26 | 27 | private String name; 28 | private String address; 29 | private String x500Address; 30 | 31 | // while parsing new properties, in some use cases the only emailaddress we get is actually encoded as name in the email 32 | // but if not, we should know when to replace it with the actually encoded address, where this flag helps us 33 | private boolean nameWasUsedAsAddress; 34 | 35 | /** 36 | * Sets the name/value pair in the {@link #properties} map. Some properties are put into special attributes (e.g., {@link #address} when the property name 37 | * is '0076'). 38 | * 39 | * @param msgProp The property to be set. 40 | */ 41 | public void setProperty(final OutlookMessageProperty msgProp) { 42 | String name = msgProp.getClazz(); 43 | final Object value = msgProp.getData(); 44 | 45 | if (name == null || value == null) { 46 | return; 47 | } 48 | name = name.intern(); 49 | 50 | int mapiClass = -1; 51 | try { 52 | mapiClass = parseInt(name, 16); 53 | } catch (final NumberFormatException e) { 54 | LOGGER.error("Unexpected mapi class: {}", name, e); 55 | } 56 | 57 | if (mapiClass == 0x3003 || mapiClass == 0x39fe || mapiClass == 0x3001) { 58 | handleNameAddressProperty(mapiClass, (String) value); 59 | } 60 | 61 | // save all properties (incl. those identified above) 62 | properties.put(mapiClass, value); 63 | } 64 | 65 | private void handleNameAddressProperty(final int mapiClass, final String probablyNamePossiblyAddress) { 66 | if (mapiClass == 0x3001) { // name 67 | handleNameProperty(probablyNamePossiblyAddress); 68 | } else if (mapiClass == 0x3003 || mapiClass == 0x39fe) { // address 69 | handleAddressProperty(probablyNamePossiblyAddress); 70 | } 71 | } 72 | 73 | private void handleNameProperty(final String probablyNamePossiblyAddress) { 74 | setName(probablyNamePossiblyAddress); 75 | // If no name+email was given, Outlook will encode the value as name, even if it actually is an addres 76 | // so just in that case, do a quick check to catch most use-cases where the name is actually the email address 77 | if (address == null && probablyNamePossiblyAddress.contains("@")) { 78 | setAddress(probablyNamePossiblyAddress); 79 | nameWasUsedAsAddress = true; 80 | } 81 | } 82 | 83 | private void handleAddressProperty(final String probablyNamePossiblyAddress) { 84 | if (probablyNamePossiblyAddress.contains("@") && (address == null || nameWasUsedAsAddress || address.matches(X500_ADDRESS_PATTERN))) { 85 | setAddress(probablyNamePossiblyAddress); 86 | nameWasUsedAsAddress = false; 87 | } else if (probablyNamePossiblyAddress.matches(X500_ADDRESS_PATTERN)) { 88 | if (address == null) { 89 | setAddress(probablyNamePossiblyAddress); 90 | } 91 | setX500Address(probablyNamePossiblyAddress); 92 | nameWasUsedAsAddress = false; 93 | } 94 | } 95 | 96 | @Override 97 | public boolean equals(final Object o) { 98 | if (this == o) { 99 | return true; 100 | } 101 | if (o == null || getClass() != o.getClass()) { 102 | return false; 103 | } 104 | final OutlookRecipient that = (OutlookRecipient) o; 105 | return Objects.equals(address, that.address) && 106 | Objects.equals(x500Address, that.x500Address) && 107 | Objects.equals(name, that.name); 108 | } 109 | 110 | @Override 111 | public int hashCode() { 112 | return Objects.hash(address, x500Address, name); 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | final StringBuilder sb = new StringBuilder(); 118 | sb.append(name); 119 | if (sb.length() > 0) { 120 | sb.append(" "); 121 | } 122 | if (address != null && !address.isEmpty()) { 123 | sb.append("<").append(address).append(">"); 124 | } 125 | if (x500Address != null && !x500Address.isEmpty()) { 126 | sb.append("<").append(x500Address).append(">"); 127 | } 128 | return sb.toString(); 129 | } 130 | 131 | /** 132 | * @return All available keys for properties found. 133 | */ 134 | public Set getPropertyCodes() { 135 | return properties.keySet(); 136 | } 137 | 138 | /** 139 | * Bean getter for {@link #address}. 140 | */ 141 | public String getAddress() { 142 | return address; 143 | } 144 | 145 | /** 146 | * Bean getter for {@link #x500Address}. 147 | */ 148 | public String getX500Address() { 149 | return x500Address; 150 | } 151 | 152 | /** 153 | * Bean setter for {@link #address}. 154 | */ 155 | public void setAddress(final String address) { 156 | this.address = address; 157 | } 158 | 159 | /** 160 | * Bean setter for {@link #address}. 161 | */ 162 | public void setX500Address(final String x500Address) { 163 | this.x500Address = x500Address; 164 | } 165 | 166 | /** 167 | * Bean getter for {@link #name}. 168 | */ 169 | public String getName() { 170 | return name; 171 | } 172 | 173 | /** 174 | * Bean setter for {@link #name}. 175 | */ 176 | public void setName(final String name) { 177 | if (name != null) { 178 | this.name = name; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/com/sun/mail/util/LineInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.com.sun.mail.util; 18 | 19 | import java.io.*; 20 | import java.nio.ByteBuffer; 21 | import java.nio.charset.StandardCharsets; 22 | import java.nio.charset.CharsetDecoder; 23 | import java.nio.charset.CodingErrorAction; 24 | import java.nio.charset.CharacterCodingException; 25 | 26 | /** 27 | * LineInputStream supports reading CRLF terminated lines that 28 | * contain only US-ASCII characters from an input stream. Provides 29 | * functionality that is similar to the deprecated 30 | * DataInputStream.readLine(). Expected use is to read 31 | * lines as String objects from an IMAP/SMTP/etc. stream.

32 | * 33 | * This class also supports UTF-8 data by calling the appropriate 34 | * constructor. Or, if the System property mail.mime.allowutf8 35 | * is set to true, an attempt will be made to interpret the data as UTF-8, 36 | * falling back to treating it as an 8-bit charset if that fails.

37 | * 38 | * LineInputStream is implemented as a FilterInputStream, so one can just 39 | * wrap it around any input stream and read bytes from this filter. 40 | * 41 | * @author John Mani 42 | * @author Bill Shannon 43 | */ 44 | 45 | public class LineInputStream extends FilterInputStream { 46 | 47 | private boolean allowutf8; 48 | private byte[] lineBuffer = null; // reusable byte buffer 49 | private CharsetDecoder decoder; 50 | 51 | private static boolean defaultutf8 = 52 | PropUtil.getBooleanSystemProperty("mail.mime.allowutf8", false); 53 | private static int MAX_INCR = 1024*1024; // 1MB 54 | 55 | public LineInputStream(InputStream in) { 56 | this(in, false); 57 | } 58 | 59 | /** 60 | * @param in the InputStream 61 | * @param allowutf8 allow UTF-8 characters? 62 | * @since JavaMail 1.6 63 | */ 64 | public LineInputStream(InputStream in, boolean allowutf8) { 65 | super(in); 66 | this.allowutf8 = allowutf8; 67 | if (!allowutf8 && defaultutf8) { 68 | decoder = StandardCharsets.UTF_8.newDecoder(); 69 | decoder.onMalformedInput(CodingErrorAction.REPORT); 70 | decoder.onUnmappableCharacter(CodingErrorAction.REPORT); 71 | } 72 | } 73 | 74 | /** 75 | * Read a line containing only ASCII characters from the input 76 | * stream. A line is terminated by a CR or NL or CR-NL sequence. 77 | * A common error is a CR-CR-NL sequence, which will also terminate 78 | * a line. 79 | * The line terminator is not returned as part of the returned 80 | * String. Returns null if no data is available.

81 | * 82 | * This class is similar to the deprecated 83 | * DataInputStream.readLine() 84 | * 85 | * @return the line 86 | * @exception IOException for I/O errors 87 | */ 88 | @SuppressWarnings("deprecation") // for old String constructor 89 | public String readLine() throws IOException { 90 | //InputStream in = this.in; 91 | byte[] buf = lineBuffer; 92 | 93 | if (buf == null) 94 | buf = lineBuffer = new byte[128]; 95 | 96 | int c1; 97 | int room = buf.length; 98 | int offset = 0; 99 | 100 | while ((c1 = in.read()) != -1) { 101 | if (c1 == '\n') // Got NL, outa here. 102 | break; 103 | else if (c1 == '\r') { 104 | // Got CR, is the next char NL ? 105 | boolean twoCRs = false; 106 | if (in.markSupported()) 107 | in.mark(2); 108 | int c2 = in.read(); 109 | if (c2 == '\r') { // discard extraneous CR 110 | twoCRs = true; 111 | c2 = in.read(); 112 | } 113 | if (c2 != '\n') { 114 | /* 115 | * If the stream supports it (which we hope will always 116 | * be the case), reset to after the first CR. Otherwise, 117 | * we wrap a PushbackInputStream around the stream so we 118 | * can unread the characters we don't need. The only 119 | * problem with that is that the caller might stop 120 | * reading from this LineInputStream, throw it away, 121 | * and then start reading from the underlying stream. 122 | * If that happens, the pushed back characters will be 123 | * lost forever. 124 | */ 125 | if (in.markSupported()) 126 | in.reset(); 127 | else { 128 | if (!(in instanceof PushbackInputStream)) 129 | in /*= this.in*/ = new PushbackInputStream(in, 2); 130 | if (c2 != -1) 131 | ((PushbackInputStream)in).unread(c2); 132 | if (twoCRs) 133 | ((PushbackInputStream)in).unread('\r'); 134 | } 135 | } 136 | break; // outa here. 137 | } 138 | 139 | // Not CR, NL or CR-NL ... 140 | // .. Insert the byte into our byte buffer 141 | if (--room < 0) { // No room, need to grow. 142 | if (buf.length < MAX_INCR) 143 | buf = new byte[buf.length * 2]; 144 | else 145 | buf = new byte[buf.length + MAX_INCR]; 146 | room = buf.length - offset - 1; 147 | System.arraycopy(lineBuffer, 0, buf, 0, offset); 148 | lineBuffer = buf; 149 | } 150 | buf[offset++] = (byte)c1; 151 | } 152 | 153 | if ((c1 == -1) && (offset == 0)) 154 | return null; 155 | 156 | if (allowutf8) 157 | return new String(buf, 0, offset, StandardCharsets.UTF_8); 158 | else { 159 | if (defaultutf8) { 160 | // try to decode it as UTF-8 161 | try { 162 | return decoder.decode(ByteBuffer.wrap(buf, 0, offset)). 163 | toString(); 164 | } catch (CharacterCodingException cex) { 165 | // looks like it's not valid UTF-8 data, 166 | // fall through and treat it as an 8-bit charset 167 | } 168 | } 169 | return new String(buf, 0, 0, offset); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/com/sun/mail/util/QPEncoderStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.com.sun.mail.util; 18 | 19 | import java.io.*; 20 | 21 | /** 22 | * This class implements a Quoted Printable Encoder. It is implemented as 23 | * a FilterOutputStream, so one can just wrap this class around 24 | * any output stream and write bytes into this filter. The Encoding 25 | * is done as the bytes are written out. 26 | * 27 | * @author John Mani 28 | */ 29 | 30 | public class QPEncoderStream extends FilterOutputStream { 31 | private int count = 0; // number of bytes that have been output 32 | private int bytesPerLine; // number of bytes per line 33 | private boolean gotSpace = false; 34 | private boolean gotCR = false; 35 | 36 | /** 37 | * Create a QP encoder that encodes the specified input stream 38 | * @param out the output stream 39 | * @param bytesPerLine the number of bytes per line. The encoder 40 | * inserts a CRLF sequence after this many number 41 | * of bytes. 42 | */ 43 | public QPEncoderStream(OutputStream out, int bytesPerLine) { 44 | super(out); 45 | // Subtract 1 to account for the '=' in the soft-return 46 | // at the end of a line 47 | this.bytesPerLine = bytesPerLine - 1; 48 | } 49 | 50 | /** 51 | * Create a QP encoder that encodes the specified input stream. 52 | * Inserts the CRLF sequence after outputting 76 bytes. 53 | * @param out the output stream 54 | */ 55 | public QPEncoderStream(OutputStream out) { 56 | this(out, 76); 57 | } 58 | 59 | /** 60 | * Encodes len bytes from the specified 61 | * byte array starting at offset off to 62 | * this output stream. 63 | * 64 | * @param b the data. 65 | * @param off the start offset in the data. 66 | * @param len the number of bytes to write. 67 | * @exception IOException if an I/O error occurs. 68 | */ 69 | @Override 70 | public void write(byte[] b, int off, int len) throws IOException { 71 | for (int i = 0; i < len; i++) 72 | write(b[off + i]); 73 | } 74 | 75 | /** 76 | * Encodes b.length bytes to this output stream. 77 | * @param b the data to be written. 78 | * @exception IOException if an I/O error occurs. 79 | */ 80 | @Override 81 | public void write(byte[] b) throws IOException { 82 | write(b, 0, b.length); 83 | } 84 | 85 | /** 86 | * Encodes the specified byte to this output stream. 87 | * @param c the byte. 88 | * @exception IOException if an I/O error occurs. 89 | */ 90 | @Override 91 | public void write(int c) throws IOException { 92 | c = c & 0xff; // Turn off the MSB. 93 | if (gotSpace) { // previous character was 94 | if (c == '\r' || c == '\n') 95 | // if CR/LF, we need to encode the char 96 | output(' ', true); 97 | else // no encoding required, just output the char 98 | output(' ', false); 99 | gotSpace = false; 100 | } 101 | 102 | if (c == '\r') { 103 | gotCR = true; 104 | outputCRLF(); 105 | } else { 106 | if (c == '\n') { 107 | if (gotCR) 108 | // This is a CRLF sequence, we already output the 109 | // corresponding CRLF when we got the CR, so ignore this 110 | ; 111 | else 112 | outputCRLF(); 113 | } else if (c == ' ') { 114 | gotSpace = true; 115 | } else if (c < 040 || c >= 0177 || c == '=') 116 | // Encoding required. 117 | output(c, true); 118 | else // No encoding required 119 | output(c, false); 120 | // whatever it was, it wasn't a CR 121 | gotCR = false; 122 | } 123 | } 124 | 125 | /** 126 | * Flushes this output stream and forces any buffered output bytes 127 | * to be encoded out to the stream. 128 | * @exception IOException if an I/O error occurs. 129 | */ 130 | @Override 131 | public void flush() throws IOException { 132 | if (gotSpace) { 133 | output(' ', true); 134 | gotSpace = false; 135 | } 136 | out.flush(); 137 | } 138 | 139 | /** 140 | * Forces any buffered output bytes to be encoded out to the stream 141 | * and closes this output stream. 142 | * 143 | * @exception IOException for I/O errors 144 | */ 145 | @Override 146 | public void close() throws IOException { 147 | flush(); 148 | out.close(); 149 | } 150 | 151 | private void outputCRLF() throws IOException { 152 | out.write('\r'); 153 | out.write('\n'); 154 | count = 0; 155 | } 156 | 157 | // The encoding table 158 | private final static char hex[] = { 159 | '0','1', '2', '3', '4', '5', '6', '7', 160 | '8','9', 'A', 'B', 'C', 'D', 'E', 'F' 161 | }; 162 | 163 | protected void output(int c, boolean encode) throws IOException { 164 | if (encode) { 165 | if ((count += 3) > bytesPerLine) { 166 | out.write('='); 167 | out.write('\r'); 168 | out.write('\n'); 169 | count = 3; // set the next line's length 170 | } 171 | out.write('='); 172 | out.write(hex[c >> 4]); 173 | out.write(hex[c & 0xf]); 174 | } else { 175 | if (++count > bytesPerLine) { 176 | out.write('='); 177 | out.write('\r'); 178 | out.write('\n'); 179 | count = 1; // set the next line's length 180 | } 181 | out.write(c); 182 | } 183 | } 184 | 185 | /**** begin TEST program *** 186 | public static void main(String argv[]) throws Exception { 187 | FileInputStream infile = new FileInputStream(argv[0]); 188 | QPEncoderStream encoder = new QPEncoderStream(System.out); 189 | int c; 190 | 191 | while ((c = infile.read()) != -1) 192 | encoder.write(c); 193 | encoder.close(); 194 | } 195 | *** end TEST program ***/ 196 | } 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![APACHE v2 License](https://img.shields.io/badge/license-apachev2-blue.svg?style=flat)](LICENSE-2.0.txt) 2 | [![Latest Release](https://img.shields.io/maven-central/v/org.simplejavamail/outlook-message-parser.svg?style=flat)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.simplejavamail%22%20AND%20a%3A%22outlook-message-parser%22) 3 | [![Javadocs](http://www.javadoc.io/badge/org.simplejavamail/outlook-message-parser.svg)](http://www.javadoc.io/doc/org.simplejavamail/outlook-message-parser) 4 | [![Codacy](https://img.shields.io/codacy/grade/db23d489d8374704a7a7e145f2dc6129?style=flat)](https://www.codacy.com/app/b-bottema/outlook-message-parser) 5 | 6 | # Outlook Message Parser 7 | *Outlook Message Parser* is a small open source Java library that parses Outlook .msg files. 8 | 9 | ```xml 10 | 11 | org.simplejavamail 12 | outlook-message-parser 13 | 1.14.1 14 | 15 | ``` 16 | 17 | Outlook Message Parser is a continuation (or fork if that project independently continues) of [msgparser](https://github.com/bbottema/msgparser). 18 | 19 | Under the hood it uses the [Apache POI - POIFS](http://poi.apache.org/poifs/) library to parse the message files which use the OLE 2 Compound Document format. Thus, it is merely a convenience library that covers the details of the .msg file. The implementation is based on the information provided at [fileformat.info](http://www.fileformat.info/format/outlookmsg/). 20 | 21 | v1.14.0 - [v1.14.1](https://search.maven.org/#artifactdetails%7Corg.simplejavamail%7Coutlook-message-parser%7C1.14.1%7Cjar) 22 | 23 | - 1.14.1 (08-06-2024): [#64](https://github.com/bbottema/outlook-message-parser/issues/64): [Bug] Parsing lists to HTML has double bullet points 24 | - 1.14.0 (25-05-2024): [#80](https://github.com/bbottema/outlook-message-parser/issues/80): RTF converted to HTML doesn't always detect charset properly 25 | 26 | 27 | v1.13.0 - [v1.13.4](https://search.maven.org/#artifactdetails%7Corg.simplejavamail%7Coutlook-message-parser%7C1.13.4%7Cjar) 28 | 29 | - 1.13.4 (04-May-2024): bumped apache poi to 5.2.5 and managed commons-io to 2.16.1 30 | - 1.13.3 (04-May-2024): bumped angus-activation from 2.0.2 to 2.0.3 31 | - 1.13.2 (05-April-2024): [#73 B](https://github.com/bbottema/outlook-message-parser/issues/73): Don't overwrite existing address, but do retain X500 address if available 32 | - 1.13.1 (04-April-2024): [#73 A](https://github.com/bbottema/outlook-message-parser/issues/73): Further improve X500 addresses detection 33 | - 1.13.0 (18-January-2024): [#71](https://github.com/bbottema/outlook-message-parser/issues/71): Update to latest Jakarta+Angus dependencies 34 | 35 | 36 | [v1.12.0](https://search.maven.org/#artifactdetails%7Corg.simplejavamail%7Coutlook-message-parser%7C1.12.0%7Cjar) (10-December-2023) 37 | 38 | - [#70](https://github.com/bbottema/outlook-message-parser/issues/70): [Enhancement] ignore recipients with null-address 39 | 40 | 41 | v1.11.0 - [v1.11.1](https://search.maven.org/#artifactdetails%7Corg.simplejavamail%7Coutlook-message-parser%7C1.11.1%7Cjar) 42 | 43 | - 1.11.1 (08-December-2023): [#69](https://github.com/bbottema/outlook-message-parser/pull/69): Enhancement: instead of ignoring them completely, only ignore for embedded images 44 | - 1.11.0 (08-December-2023): [#69](https://github.com/bbottema/outlook-message-parser/pull/69): Enhancement: ignore attachment with missing content 45 | 46 | 47 | v1.10.0 - [v1.10.2](https://search.maven.org/#artifactdetails%7Corg.simplejavamail%7Coutlook-message-parser%7C1.10.2%7Cjar) 48 | 49 | - 1.10.2 (03-December-2023): [#68](https://github.com/bbottema/outlook-message-parser/pull/68) Improved heuristics for X500 Names 50 | - 1.10.1 (24-October-2023): [#67](https://github.com/bbottema/outlook-message-parser/pull/67) Fixed "possibility to parse X500 Names" 51 | - 1.10.0 (24-October-2023): [#67](https://github.com/bbottema/outlook-message-parser/pull/67) Adding possibility to parse X500 Names (dont' use this version) 52 | 53 | 54 | v1.9.0 - [v1.9.6](https://search.maven.org/#artifactdetails%7Corg.simplejavamail%7Coutlook-message-parser%7C1.9.6%7Cjar) 55 | 56 | - v1.9.6 (18-July-2022): [#57](https://github.com/bbottema/outlook-message-parser/pull/57) Same, but now with Collection values to support duplicate headers 57 | - v1.9.5 (18-July-2022): [#57](https://github.com/bbottema/outlook-message-parser/pull/57) Headers should be more accessible, rather than just a big string of text 58 | - v1.9.x - a bunch of dependency fixes and tries apparently, my release train was not so smooth here, sorry 59 | - v1.9.0 (13-May-2021): [#55](https://github.com/bbottema/outlook-message-parser/pull/55) CVE issue: Update Apache POI and POI Scratchpad 60 | 61 | 62 | v1.8.0 - [v1.8.1](https://search.maven.org/#artifactdetails%7Corg.simplejavamail%7Coutlook-message-parser%7C1.8.1%7Cjar) 63 | 64 | - v1.8.1 (31-January-2022): [#41](https://github.com/bbottema/outlook-message-parser/pull/41) OutlookMessage.getPropertyValue() should be public 65 | - v1.8.0 (31-January-2022): [#52](https://github.com/bbottema/outlook-message-parser/pull/52) Adjust dependencies and make Java 9+ friendly 66 | - v1.8.0 (31-January-2022): [#45](https://github.com/bbottema/outlook-message-parser/pull/45) Bump commons-io from 2.6 to 2.7 67 | 68 | 69 | v1.7.10 - v1.7.13 (17-November-2021) 70 | 71 | - [#49](https://github.com/bbottema/outlook-message-parser/issues/49) bugfix solved by improved charset handling 72 | - [#46](https://github.com/bbottema/outlook-message-parser/issues/46) bugfix Rare NPE case of producing empty nested outlook attachment when there should be no attachments 73 | - [#43](https://github.com/bbottema/outlook-message-parser/issues/43) bugfix bugfix getFromEmailFromHeaders cannot handle "quoted-name-with@at-sign" 74 | - some minor code improvements 75 | 76 | 77 | v1.7.9 (10-October-2020) 78 | 79 | - [#28](https://github.com/bbottema/outlook-message-parser/issues/28) / [#36](https://github.com/bbottema/outlook-message-parser/issues/36) bugfix NumberFormatException on parsing .msg files 80 | 81 | 82 | v1.7.8 (4-August-2020) 83 | 84 | - [#35](https://github.com/bbottema/outlook-message-parser/issues/35) Clarify permission to publish project using Apache v2 license 85 | 86 | 87 | v1.7.0 - v1.7.7 (9-January-2020 - 17-July-2020) 88 | 89 | - v1.7.7 - [#34](https://github.com/bbottema/outlook-message-parser/issues/34) Wrong encoding for bodyHTML 90 | - v1.7.5 - [#31](https://github.com/bbottema/outlook-message-parser/issues/31) Bugfix for attachments with special characters in the name 91 | - v1.7.4 - [#27](https://github.com/bbottema/outlook-message-parser/issues/27) Same as 1.7.3, but now also for chinese senders 92 | - v1.7.3 - [#27](https://github.com/bbottema/outlook-message-parser/issues/27) When from name/address are not available (unsent emails), these fields are filled with binary garbage 93 | - v1.7.2 - [#26](https://github.com/bbottema/outlook-message-parser/issues/26) To email address is not handled properly when name is omitted 94 | - v1.7.1 - [#25](https://github.com/bbottema/outlook-message-parser/issues/25) NPE on ClientSubmitTime when original message has not been sent yet 95 | - v1.7.1 - [#23](https://github.com/bbottema/outlook-message-parser/issues/23) Bug: __nameid_ directory should not be parsed (and causing invalid HTML body) 96 | - v1.7.0 - [#18](https://github.com/bbottema/outlook-message-parser/issues/18) Upgrade Apache POI 3.9 -> 4.x 97 | 98 | Note: Apache POI requires minimum Java 8 99 | 100 | 101 | v1.6.0 (8-January-2020) 102 | 103 | - [#21](https://github.com/bbottema/outlook-message-parser/issues/21) Multiple TO recipients are not handles properly 104 | 105 | 106 | v1.5.0 (18-December-2019) 107 | 108 | - [#20](https://github.com/bbottema/outlook-message-parser/issues/20) CC and BCC recipients are not parsed properly 109 | - [#19](https://github.com/bbottema/outlook-message-parser/issues/19) Use real Outlook ContentId Attribute to resolve CID Attachments 110 | 111 | 112 | v1.4.1 (22-October-2019) 113 | 114 | - [#17](https://github.com/bbottema/outlook-message-parser/issues/17) Fixed encoding error for UTF-8's Windows legacy name (cp)65001 115 | 116 | 117 | v1.4.0 (13-October-2019) 118 | 119 | - [#9](https://github.com/bbottema/outlook-message-parser/issues/9) Replaced the RFC to HTML converter with a brand new RFC-compliant convert! (thanks to @fadeyev!) 120 | 121 | 122 | v1.3.0 (4-October-2019) 123 | 124 | - [#14](https://github.com/bbottema/outlook-message-parser/issues/14) Dependency problem with Java9+, missing Jakarta Activation Framework 125 | - [#13](https://github.com/bbottema/outlook-message-parser/issues/13) HTML start tags with extra space not handled correctly 126 | - [#11](https://github.com/bbottema/outlook-message-parser/issues/11) SimpleRTF2HTMLConverter inserts too many
tags 127 | - [#10](https://github.com/bbottema/outlook-message-parser/issues/10) Embedded images with DOS-like names are classified as attachments 128 | - [#9](https://github.com/bbottema/outlook-message-parser/issues/9) SimpleRTF2HTMLConverter removes some valid tags during conversion 129 | 130 | 131 | v1.2.1 (12-May-2019) 132 | 133 | - Ignore non S/MIME related content types when extracting S/MIME metadata 134 | - Added toString and equals methods to the S/MIME data classes 135 | 136 | 137 | v1.1.21 (4-May-2019) 138 | 139 | - Upgraded mediatype recognition based on file extension for incomplete attachments 140 | - Added / improved support for public S/MIME meta data 141 | 142 | 143 | v1.1.20 (14-April-2019) 144 | 145 | - [#7](https://github.com/bbottema/outlook-message-parser/issues/7) Fix missing S/MIME header details that are needed to determine the type of S/MIME application 146 | 147 | 148 | v1.1.19 (10-April-2019) 149 | 150 | - Log rtf compression error, but otherwise ignore it and keep going and extract what we can. 151 | 152 | 153 | v1.1.18 (5-April-2019) 154 | 155 | - [#6](https://github.com/bbottema/outlook-message-parser/issues/6) Missing mimeTag for attachments should be guessed based on file extension 156 | 157 | 158 | v1.1.17 (19-August-2018) 159 | 160 | - [#3](https://github.com/bbottema/simple-java-mail/issues/3) implemented robust support for character sets / code pages in RTF to HTML 161 | conversion (fixes chinese support #3) 162 | - fixed bug where too much text was cleaned up as part of superfluous RTF cleanup step when converting to HTML 163 | - Performance boost in the RTF -> HTML converter 164 | 165 | 166 | v1.1.16 (~28-Februari-2017) 167 | 168 | - First Maven deployment, continuing version number from 1.1.15 of msgparser (https://github.com/bbottema/msgparser) 169 | 170 | 171 | v1.16 172 | - Added support for replyTo name and address 173 | - cleaned up code (1st wave) -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/com/sun/mail/util/BASE64EncoderStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.com.sun.mail.util; 18 | 19 | import java.io.*; 20 | 21 | /** 22 | * This class implements a BASE64 encoder. It is implemented as 23 | * a FilterOutputStream, so one can just wrap this class around 24 | * any output stream and write bytes into this filter. The encoding 25 | * is done as the bytes are written out. 26 | * 27 | * @author John Mani 28 | * @author Bill Shannon 29 | */ 30 | 31 | public class BASE64EncoderStream extends FilterOutputStream { 32 | private byte[] buffer; // cache of bytes that are yet to be encoded 33 | private int bufsize = 0; // size of the cache 34 | private byte[] outbuf; // line size output buffer 35 | private int count = 0; // number of bytes that have been output 36 | private int bytesPerLine; // number of bytes per line 37 | private int lineLimit; // number of input bytes to output bytesPerLine 38 | private boolean noCRLF = false; 39 | 40 | private static byte[] newline = new byte[] { '\r', '\n' }; 41 | 42 | /** 43 | * Create a BASE64 encoder that encodes the specified output stream. 44 | * 45 | * @param out the output stream 46 | * @param bytesPerLine number of bytes per line. The encoder inserts 47 | * a CRLF sequence after the specified number of bytes, 48 | * unless bytesPerLine is Integer.MAX_VALUE, in which 49 | * case no CRLF is inserted. bytesPerLine is rounded 50 | * down to a multiple of 4. 51 | */ 52 | public BASE64EncoderStream(OutputStream out, int bytesPerLine) { 53 | super(out); 54 | buffer = new byte[3]; 55 | if (bytesPerLine == Integer.MAX_VALUE || bytesPerLine < 4) { 56 | noCRLF = true; 57 | bytesPerLine = 76; 58 | } 59 | bytesPerLine = (bytesPerLine / 4) * 4; // Rounded down to 4n 60 | this.bytesPerLine = bytesPerLine; // save it 61 | lineLimit = bytesPerLine / 4 * 3; 62 | 63 | if (noCRLF) { 64 | outbuf = new byte[bytesPerLine]; 65 | } else { 66 | outbuf = new byte[bytesPerLine + 2]; 67 | outbuf[bytesPerLine] = (byte)'\r'; 68 | outbuf[bytesPerLine + 1] = (byte)'\n'; 69 | } 70 | } 71 | 72 | /** 73 | * Create a BASE64 encoder that encodes the specified input stream. 74 | * Inserts the CRLF sequence after outputting 76 bytes. 75 | * 76 | * @param out the output stream 77 | */ 78 | public BASE64EncoderStream(OutputStream out) { 79 | this(out, 76); 80 | } 81 | 82 | /** 83 | * Encodes len bytes from the specified 84 | * byte array starting at offset off to 85 | * this output stream. 86 | * 87 | * @param b the data. 88 | * @param off the start offset in the data. 89 | * @param len the number of bytes to write. 90 | * @exception IOException if an I/O error occurs. 91 | */ 92 | @Override 93 | public synchronized void write(byte[] b, int off, int len) 94 | throws IOException { 95 | int end = off + len; 96 | 97 | // finish off incomplete coding unit 98 | while (bufsize != 0 && off < end) 99 | write(b[off++]); 100 | 101 | // finish off line 102 | int blen = ((bytesPerLine - count) / 4) * 3; 103 | if (off + blen <= end) { 104 | // number of bytes that will be produced in outbuf 105 | int outlen = encodedSize(blen); 106 | if (!noCRLF) { 107 | outbuf[outlen++] = (byte)'\r'; 108 | outbuf[outlen++] = (byte)'\n'; 109 | } 110 | out.write(encode(b, off, blen, outbuf), 0, outlen); 111 | off += blen; 112 | count = 0; 113 | } 114 | 115 | // do bulk encoding a line at a time. 116 | for (; off + lineLimit <= end; off += lineLimit) 117 | out.write(encode(b, off, lineLimit, outbuf)); 118 | 119 | // handle remaining partial line 120 | if (off + 3 <= end) { 121 | blen = end - off; 122 | blen = (blen / 3) * 3; // round down 123 | // number of bytes that will be produced in outbuf 124 | int outlen = encodedSize(blen); 125 | out.write(encode(b, off, blen, outbuf), 0, outlen); 126 | off += blen; 127 | count += outlen; 128 | } 129 | 130 | // start next coding unit 131 | for (; off < end; off++) 132 | write(b[off]); 133 | } 134 | 135 | /** 136 | * Encodes b.length bytes to this output stream. 137 | * 138 | * @param b the data to be written. 139 | * @exception IOException if an I/O error occurs. 140 | */ 141 | @Override 142 | public void write(byte[] b) throws IOException { 143 | write(b, 0, b.length); 144 | } 145 | 146 | /** 147 | * Encodes the specified byte to this output stream. 148 | * 149 | * @param c the byte. 150 | * @exception IOException if an I/O error occurs. 151 | */ 152 | @Override 153 | public synchronized void write(int c) throws IOException { 154 | buffer[bufsize++] = (byte)c; 155 | if (bufsize == 3) { // Encoding unit = 3 bytes 156 | encode(); 157 | bufsize = 0; 158 | } 159 | } 160 | 161 | /** 162 | * Flushes this output stream and forces any buffered output bytes 163 | * to be encoded out to the stream. 164 | * 165 | * @exception IOException if an I/O error occurs. 166 | */ 167 | @Override 168 | public synchronized void flush() throws IOException { 169 | if (bufsize > 0) { // If there's unencoded characters in the buffer .. 170 | encode(); // .. encode them 171 | bufsize = 0; 172 | } 173 | out.flush(); 174 | } 175 | 176 | /** 177 | * Forces any buffered output bytes to be encoded out to the stream 178 | * and closes this output stream 179 | */ 180 | @Override 181 | public synchronized void close() throws IOException { 182 | flush(); 183 | if (count > 0 && !noCRLF) { 184 | out.write(newline); 185 | out.flush(); 186 | } 187 | out.close(); 188 | } 189 | 190 | /** This array maps the characters to their 6 bit values */ 191 | private final static char pem_array[] = { 192 | 'A','B','C','D','E','F','G','H', // 0 193 | 'I','J','K','L','M','N','O','P', // 1 194 | 'Q','R','S','T','U','V','W','X', // 2 195 | 'Y','Z','a','b','c','d','e','f', // 3 196 | 'g','h','i','j','k','l','m','n', // 4 197 | 'o','p','q','r','s','t','u','v', // 5 198 | 'w','x','y','z','0','1','2','3', // 6 199 | '4','5','6','7','8','9','+','/' // 7 200 | }; 201 | 202 | /** 203 | * Encode the data stored in buffer. 204 | * Uses outbuf to store the encoded 205 | * data before writing. 206 | * 207 | * @exception IOException if an I/O error occurs. 208 | */ 209 | private void encode() throws IOException { 210 | int osize = encodedSize(bufsize); 211 | out.write(encode(buffer, 0, bufsize, outbuf), 0, osize); 212 | // increment count 213 | count += osize; 214 | // If writing out this encoded unit caused overflow, 215 | // start a new line. 216 | if (count >= bytesPerLine) { 217 | if (!noCRLF) 218 | out.write(newline); 219 | count = 0; 220 | } 221 | } 222 | 223 | /** 224 | * Base64 encode a byte array. No line breaks are inserted. 225 | * This method is suitable for short strings, such as those 226 | * in the IMAP AUTHENTICATE protocol, but not to encode the 227 | * entire content of a MIME part. 228 | * 229 | * @param inbuf the byte array 230 | * @return the encoded byte array 231 | */ 232 | public static byte[] encode(byte[] inbuf) { 233 | if (inbuf.length == 0) 234 | return inbuf; 235 | return encode(inbuf, 0, inbuf.length, null); 236 | } 237 | 238 | /** 239 | * Internal use only version of encode. Allow specifying which 240 | * part of the input buffer to encode. If outbuf is non-null, 241 | * it's used as is. Otherwise, a new output buffer is allocated. 242 | */ 243 | private static byte[] encode(byte[] inbuf, int off, int size, 244 | byte[] outbuf) { 245 | if (outbuf == null) 246 | outbuf = new byte[encodedSize(size)]; 247 | int inpos, outpos; 248 | int val; 249 | for (inpos = off, outpos = 0; size >= 3; size -= 3, outpos += 4) { 250 | val = inbuf[inpos++] & 0xff; 251 | val <<= 8; 252 | val |= inbuf[inpos++] & 0xff; 253 | val <<= 8; 254 | val |= inbuf[inpos++] & 0xff; 255 | outbuf[outpos+3] = (byte)pem_array[val & 0x3f]; 256 | val >>= 6; 257 | outbuf[outpos+2] = (byte)pem_array[val & 0x3f]; 258 | val >>= 6; 259 | outbuf[outpos+1] = (byte)pem_array[val & 0x3f]; 260 | val >>= 6; 261 | outbuf[outpos+0] = (byte)pem_array[val & 0x3f]; 262 | } 263 | // done with groups of three, finish up any odd bytes left 264 | if (size == 1) { 265 | val = inbuf[inpos++] & 0xff; 266 | val <<= 4; 267 | outbuf[outpos+3] = (byte)'='; // pad character; 268 | outbuf[outpos+2] = (byte)'='; // pad character; 269 | outbuf[outpos+1] = (byte)pem_array[val & 0x3f]; 270 | val >>= 6; 271 | outbuf[outpos+0] = (byte)pem_array[val & 0x3f]; 272 | } else if (size == 2) { 273 | val = inbuf[inpos++] & 0xff; 274 | val <<= 8; 275 | val |= inbuf[inpos++] & 0xff; 276 | val <<= 2; 277 | outbuf[outpos+3] = (byte)'='; // pad character; 278 | outbuf[outpos+2] = (byte)pem_array[val & 0x3f]; 279 | val >>= 6; 280 | outbuf[outpos+1] = (byte)pem_array[val & 0x3f]; 281 | val >>= 6; 282 | outbuf[outpos+0] = (byte)pem_array[val & 0x3f]; 283 | } 284 | return outbuf; 285 | } 286 | 287 | /** 288 | * Return the corresponding encoded size for the given number 289 | * of bytes, not including any CRLF. 290 | */ 291 | private static int encodedSize(int size) { 292 | return ((size + 2) / 3) * 4; 293 | } 294 | 295 | /*** begin TEST program 296 | public static void main(String argv[]) throws Exception { 297 | FileInputStream infile = new FileInputStream(argv[0]); 298 | BASE64EncoderStream encoder = new BASE64EncoderStream(System.out); 299 | int c; 300 | 301 | while ((c = infile.read()) != -1) 302 | encoder.write(c); 303 | encoder.close(); 304 | } 305 | *** end TEST program **/ 306 | } 307 | -------------------------------------------------------------------------------- /LICENSE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. -------------------------------------------------------------------------------- /src/main/resources/mimetypes.txt: -------------------------------------------------------------------------------- 1 | xgl/movie xmz 2 | xgl/drawing xgz 3 | x-world/x-vrt vrt 4 | x-world/x-vrml wrl vrml wrz 5 | x-world/x-svr svr 6 | x-world/x-3dmf qd3d qd3 3dmf 3dm 7 | x-music/x-midi midi mid 8 | x-conference/x-cooltalk ice 9 | www/mime mime 10 | windows/metafile wmf 11 | video/x-sgi-movie movie mv 12 | video/x-scm scm 13 | video/x-qtc qtc 14 | video/x-msvideo avi 15 | video/x-ms-asf-plugin asx 16 | video/x-ms-asf asf asx 17 | video/x-mpeq2a mp2 18 | video/x-mpeg mp3 mp2 19 | video/x-motion-jpeg mjpg 20 | video/x-isvideo isu 21 | video/x-gl gl 22 | video/x-generic mp4 wmv swf dvd mov mpeg osp ogv 23 | video/x-fli fli 24 | video/x-dv dif dv 25 | video/x-dl dl 26 | video/x-atomic3d-feature fmf 27 | video/x-amt-showrun xsr 28 | video/x-amt-demorun xdr 29 | video/vosaic vos 30 | video/vnd.vivo viv vivo 31 | video/vnd.rn-realvideo rv 32 | video/vivo viv vivo 33 | video/vdo vdo 34 | video/quicktime qt mov moov 35 | video/msvideo avi 36 | video/mpeg m2v mp3 mpv m1v mpe mpg mpegv mpeg vbs mp2 mpa 37 | video/gl gl 38 | video/fli fli 39 | video/dl dl 40 | video/avs-video avs 41 | video/avi avi 42 | video/animaflex afl 43 | text/xml xml XML osm 44 | text/x-vcalendar vcs 45 | text/x-uuencode uu uue 46 | text/x-uil uil 47 | text/x-tex tex 48 | text/x-tcl tcl 49 | text/x-sql sql 50 | text/x-speech spc talk 51 | text/x-sgml sgml sgm 52 | text/x-setext etx 53 | text/x-server-parsed-html ssi shtml 54 | text/x-script.zsh zsh 55 | text/x-script.tcsh tcsh 56 | text/x-script.tcl tcl 57 | text/x-script.sh sh 58 | text/x-script.scheme scm 59 | text/x-script.rexx rexx 60 | text/x-script.phyton py 61 | text/x-script.perl-module pm 62 | text/x-script.perl pl 63 | text/x-script.lisp lsp 64 | text/x-script.ksh ksh 65 | text/x-script.guile scm 66 | text/x-script.elisp el 67 | text/x-script.csh csh 68 | text/x-script hlb 69 | text/x-python py 70 | text/x-po pot po 71 | text/x-pascal p pas 72 | text/x-m m 73 | text/x-log log 74 | text/x-la-asf lsx 75 | text/x-java-source java jav 76 | text/x-java java 77 | text/x-h hh h 78 | text/x-fortran f f77 for f90 79 | text/x-csrc c 80 | text/x-component htc 81 | text/x-c++src cpp c++ 82 | text/x-c++hdr h 83 | text/x-c cc cpp c 84 | text/x-bibtex bib 85 | text/x-audiosoft-intra aip 86 | text/x-asm s asm 87 | text/webviewhtml htt 88 | text/vnd.wap.wmlscript wmls 89 | text/vnd.wap.wml wml 90 | text/vnd.rn-realtext rt 91 | text/vnd.fmi.flexstor flx 92 | text/vnd.abc abc 93 | text/uri-list uni uris unis uri 94 | text/tab-separated-values tab tsv 95 | text/sgml sgml sgm 96 | text/scriplet wsc 97 | text/rtf rtf 98 | text/richtext rt rtf rtx 99 | text/plain com hh c++ def log sdml for conf TEXT lst TXT java text jav mar cc cxx c f g h idc list m txt f90 pl 100 | text/pascal pas 101 | text/mcf mcf 102 | text/javascript js 103 | text/html htm HTM htmls acgi html HTML shtml htx 104 | text/ecmascript js 105 | text/csv csv 106 | text/css css 107 | text/asp asp 108 | paleovu/x-pv pvu 109 | music/x-karaoke kar 110 | music/crescendo midi mid 111 | multipart/x-zip zip 112 | multipart/x-ustar ustar 113 | multipart/x-gzip gzip 114 | model/x-pov pov 115 | model/vrml wrl vrml wrz 116 | model/vnd.dwf dwf 117 | model/iges igs iges 118 | message/rfc822 mime mht mhtml 119 | image/xpm xpm 120 | image/xbm xbm 121 | image/x-xwindowdump xwd 122 | image/x-xwd xwd 123 | image/x-xpixmap xpm pm 124 | image/x-xbm xbm 125 | image/x-xbitmap xbm 126 | image/x-windows-bmp bmp 127 | image/x-tiff tif tiff 128 | image/x-rgb rgb 129 | image/x-quicktime qtif qti qif 130 | image/x-portable-pixmap ppm 131 | image/x-portable-greymap pgm 132 | image/x-portable-graymap pgm 133 | image/x-portable-bitmap pbm 134 | image/x-portable-anymap pnm 135 | image/x-pict pct 136 | image/x-pcx pcx 137 | image/x-niff niff nif 138 | image/x-jps jps 139 | image/x-jg art 140 | image/x-icon ico 141 | image/x-generic jpg tif wmf tiff bmp xpm png jpeg emf 142 | image/x-eps eps 143 | image/x-dwg svf dxf dwg 144 | image/x-cmu-raster ras 145 | image/vnd.xiff xif 146 | image/vnd.wap.wbmp wbmp 147 | image/vnd.rn-realpix rp 148 | image/vnd.rn-realflash rf 149 | image/vnd.net-fpx fpx 150 | image/vnd.fpx fpx 151 | image/vnd.dwg svf dxf dwg 152 | image/vasa mcf 153 | image/tiff tif tiff 154 | image/svg+xml svgz svg 155 | image/png x-png png PNG 156 | image/pjpeg jpg jfif jpeg jpe 157 | image/pict pic pict 158 | image/naplps naplps nap 159 | image/jutvision jut 160 | image/jpeg jpg JPG jfif jpeg jfif-tbnl jpe 161 | image/ief iefs ief 162 | image/gif gif GIF 163 | image/g3fax g3 164 | image/florian flo turbot 165 | image/fif fif 166 | image/cmu-raster ras rast 167 | image/bmp bmp bm 168 | i-world/i-vrml ivr 169 | drawing/x-dwf dwf (old) 170 | chemical/x-pdb xyz pdb 171 | audio/xm xm 172 | audio/x-wav wav 173 | audio/x-voc voc 174 | audio/x-vnd.audioexplosion.mjuicemediafile mjf 175 | audio/x-twinvq-plugin vql vqe 176 | audio/x-twinvq vqf 177 | audio/x-realaudio ra 178 | audio/x-psid sid 179 | audio/x-pn-realaudio-plugin rmp rpm ra 180 | audio/x-pn-realaudio rmm rmp rm ra ram 181 | audio/x-nspaudio lma la 182 | audio/x-mpequrl m3u 183 | audio/x-mpeg-3 mp3 184 | audio/x-mpeg mpg mpeg mp2 185 | audio/x-mod mod 186 | audio/x-midi midi mid 187 | audio/x-mid midi mid 188 | audio/x-liveaudio lam 189 | audio/x-jam jam 190 | audio/x-gsm gsm gsd 191 | audio/x-generic mp3 wma wav ogg 192 | audio/x-au au 193 | audio/x-aiff aiff aifc aif 194 | audio/x-aifc aifc 195 | audio/x-adpcm snd 196 | audio/wav wav 197 | audio/voxware vox 198 | audio/voc voc 199 | audio/vnd.qcelp qcp 200 | audio/tsplayer tsp 201 | audio/tsp-audio tsi 202 | audio/s3m s3m 203 | audio/nspaudio lma la 204 | audio/mpeg3 mp3 205 | audio/mpeg mp3 mpg mpeg3 m2a mp2 mpga mpa 206 | audio/mod mod 207 | audio/midi midi mid kar 208 | audio/mid rmi 209 | audio/make.my.funk pfunk 210 | audio/make pfunk funk my 211 | audio/it it 212 | audio/basic au snd 213 | audio/aiff aiff aifc aif 214 | audio/ac3 ac3 215 | application/zip zip 216 | application/xml xml 217 | application/x-zip-compressed zip 218 | application/x-x509-user-cert crt 219 | application/x-x509-ca-cert der cer crt 220 | application/x-wri wri 221 | application/x-wpwin wpd 222 | application/x-world wrl svr 223 | application/x-wintalk wtk 224 | application/x-winhelp hlp 225 | application/x-wais-source src wsrc 226 | application/x-vrml vrml 227 | application/x-vnd.oasis.opendocument.spreadsheet ods 228 | application/x-vnd.ls-xpix xpix 229 | application/x-vnd.audioexplosion.mzz mzz 230 | application/x-visio vsd vst vsw 231 | application/x-ustar ustar 232 | application/x-troff-msvideo avi 233 | application/x-troff-ms ms 234 | application/x-troff-me me 235 | application/x-troff-man man 236 | application/x-troff t roff tr 237 | application/x-trash autosave 238 | application/x-texinfo texi texinfo 239 | application/x-tex tex 240 | application/x-tcl tcl 241 | application/x-tbook tbk sbk 242 | application/x-tar tar 243 | application/x-sv4crc sv4crc 244 | application/x-sv4cpio sv4cpio 245 | application/x-stuffit sit 246 | application/x-sprite spr sprite 247 | application/x-sit sit 248 | application/x-shockwave-flash swf 249 | application/x-shellscript sh 250 | application/x-sharedlib o 251 | application/x-shar shar sh 252 | application/x-sh sh 253 | application/x-seelogo sl 254 | application/x-sea sea 255 | application/x-sdp sdp 256 | application/x-rtf rtf 257 | application/x-rpm rpm 258 | application/x-qpro wb1 259 | application/x-project mpt mpc mpv mpx 260 | application/x-portable-anymap pnm 261 | application/x-pointplus css 262 | application/x-pkcs7-signature p7a 263 | application/x-pkcs7-mime p7m p7c 264 | application/x-pkcs7-certreqresp p7r 265 | application/x-pkcs7-certificates spc 266 | application/x-pkcs12 p12 267 | application/x-pkcs10 p10 268 | application/x-pixclscript plx 269 | application/x-php php 270 | application/x-perl pl 271 | application/x-pcl pcl 272 | application/x-pagemaker pm4 pm5 273 | application/x-omcregerator omcr 274 | application/x-omcdatamaker omcd 275 | application/x-omc omc 276 | application/x-nokia-9000-communicator-add-on-software aos 277 | application/x-newton-compatible-pkg pkg 278 | application/x-netcdf cdf nc 279 | application/x-navistyle stl 280 | application/x-navimap map 281 | application/x-navidoc nvd 282 | application/x-navi-animation ani 283 | application/x-mspowerpoint ppt 284 | application/x-msexcel xlw xla xls 285 | application/x-ms-dos-executable msi exe 286 | application/x-mplayer2 asx 287 | application/x-mix-transfer nix 288 | application/x-mif mif 289 | application/x-midi midi mid 290 | application/x-meme mm 291 | application/x-mathcad mcd 292 | application/x-magic-cap-package-1.0 mc$ 293 | application/x-macbinary bin 294 | application/x-mac-binhex40 hqx 295 | application/x-lzx lzx 296 | application/x-lzh lzh 297 | application/x-lotusscreencam scm 298 | application/x-lotus wq1 299 | application/x-livescreen ivy 300 | application/x-lisp lsp 301 | application/x-lha lha 302 | application/x-latex ltx latex 303 | application/x-ksh ksh 304 | application/x-koan skm skp skd skt 305 | application/x-javascript js 306 | application/x-java-commerce jcm 307 | application/x-java-class class 308 | application/x-java-archive jar 309 | application/x-java-applet class 310 | application/x-ip2 ip 311 | application/x-inventor iv 312 | application/x-internett-signup ins 313 | application/x-ima ima 314 | application/x-httpd-imap imap 315 | application/x-helpfile help hlp 316 | application/x-hdf hdf 317 | application/x-gzip gz gzip 318 | application/x-gtar gtar 319 | application/x-gss gss 320 | application/x-gsp gsp 321 | application/x-freelance pre 322 | application/x-frame mif 323 | application/x-font-ttf ttf TTF 324 | application/x-font-otf otf OTF 325 | application/x-excel xld xlt xlw xlv xlk xlm xll xla xlc xls xlb 326 | application/x-esrehber es 327 | application/x-envoy evy env 328 | application/x-elc elc 329 | application/x-dvi dvi 330 | application/x-director dcr dir dxr 331 | application/x-deepv deepv 332 | application/x-deb deb 333 | application/x-csh csh 334 | application/x-cpt cpt 335 | application/x-cpio cpio 336 | application/x-conference nsc 337 | application/x-compressed zip gz tgz z 338 | application/x-compress zip rar gz tar z 339 | application/x-compactpro cpt 340 | application/x-cocoa cco 341 | application/x-cmu-raster ras 342 | application/x-chat chat cha 343 | application/x-cdlink vcd 344 | application/x-cdf cdf 345 | application/x-cd-image iso 346 | application/x-bzip2 bz2 boz 347 | application/x-bzip bz 348 | application/x-bytecode.python pyc 349 | application/x-bytecode.elisp elisp) (compiled elc 350 | application/x-bsh shar sh bsh 351 | application/x-blender blend 352 | application/x-binhex40 hqx 353 | application/x-binary bin 354 | application/x-bcpio bcpio 355 | application/x-awk awk 356 | application/x-authorware-seg aas 357 | application/x-authorware-map aam 358 | application/x-authorware-bin aab 359 | application/x-aim aim 360 | application/x-123 wk1 361 | application/wordperfect6.1 w61 362 | application/wordperfect6.0 w60 wp5 363 | application/wordperfect wp wpd wp6 wp5 364 | application/vocaltec-media-file vmf 365 | application/vocaltec-media-desc vmd 366 | application/vnd.xara web 367 | application/vnd.wap.wmlscriptc wmlsc 368 | application/vnd.wap.wmlc wmlc 369 | application/vnd.rn-realplayer rnx 370 | application/vnd.rn-realmedia rm 371 | application/vnd.openxmlformats-officedocument.wordprocessingml.document docx 372 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx 373 | application/vnd.oasis.text odt 374 | application/vnd.oasis.spreadsheet ods 375 | application/vnd.oasis.presentation odp 376 | application/vnd.oasis.opendocument.text odt 377 | application/vnd.oasis.database odb 378 | application/vnd.nokia.ringing-tone rng 379 | application/vnd.nokia.configuration-message ncm 380 | application/vnd.ms-project mpp 381 | application/vnd.ms-powerpoint ppa pptx pps pwz pot ppt pptm 382 | application/vnd.ms-pki.stl stl 383 | application/vnd.ms-pki.seccat cat 384 | application/vnd.ms-pki.pko pko 385 | application/vnd.ms-pki.certstore sst 386 | application/vnd.ms-excel xlsx xlw xlsm xlm xll xlc xls XLS xlb 387 | application/vnd.hp-pcl pcl 388 | application/vnd.hp-hpgl hgl hpgl hpg 389 | application/vnd.fdf fdf 390 | application/vda vda 391 | application/toolbook tbk 392 | application/streamingmedia ssm 393 | application/step step stp 394 | application/sounder sdr 395 | application/solids sol 396 | application/smil smil smi 397 | application/sla stl 398 | application/set set 399 | application/sea sea 400 | application/sdp sdp 401 | application/rtf rtf rtx 402 | application/rss+xml rss 403 | application/ringing-tones rng 404 | application/pro_eng prt part 405 | application/powerpoint ppt 406 | application/postscript ps ai eps 407 | application/plain text 408 | application/pkix-crl crl 409 | application/pkix-cert cer crt 410 | application/pkcs7-signature p7s 411 | application/pkcs7-mime p7m p7c 412 | application/pkcs10 p10 413 | application/pkcs-crl crl 414 | application/pkcs-12 p12 415 | application/pdf pdf 416 | application/oda oda 417 | application/octet-stream com psd uu a lha bin lzx o arc exe arj dump lzh zoo lhx saveme 418 | application/netmc mcp 419 | application/mswrite wri 420 | application/msword dot doc w6w wiz docm word docx 421 | application/mspowerpoint pps pot ppt ppz 422 | application/mime aps 423 | application/mcad mcd 424 | application/mbedlet mbd 425 | application/marc mrc 426 | application/macbinary bin 427 | application/mac-compactpro cpt 428 | application/mac-binhex40 hqx 429 | application/mac-binhex hqx 430 | application/mac-binary bin 431 | application/lzx lzx 432 | application/lha lha 433 | application/javascript js 434 | application/java-byte-code class 435 | application/java class 436 | application/inf inf 437 | application/iges igs iges 438 | application/i-deas unv 439 | application/hta hta 440 | application/hlp hlp 441 | application/groupwise vew 442 | application/gnutar tgz 443 | application/futuresplash spl 444 | application/freeloader frl 445 | application/fractals fif 446 | application/excel xld xlt xlw xlv xl xlk xlm xll xla xlc xls xlb 447 | application/envoy evy 448 | application/ecmascript js 449 | application/dxf dxf 450 | application/dsptype tsp 451 | application/drafting drw 452 | application/commonground dp 453 | application/clariscad ccad 454 | application/cdf cdf 455 | application/book boo book 456 | application/binhex4 hqx 457 | application/binhex hqx 458 | application/base64 mm mme 459 | application/arj arj 460 | application/acad dwg -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/com/sun/mail/util/MailLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.com.sun.mail.util; 18 | 19 | import java.io.PrintStream; 20 | import java.text.MessageFormat; 21 | import java.util.logging.Level; 22 | import java.util.logging.Logger; 23 | 24 | /** 25 | * A simplified logger used by Jakarta Mail to handle logging to a 26 | * PrintStream and logging through a java.util.logging.Logger. 27 | * If debug is set, messages are written to the PrintStream and 28 | * prefixed by the specified prefix (which is not included in 29 | * Logger messages). 30 | * Messages are logged by the Logger based on the configuration 31 | * of the logging system. 32 | */ 33 | 34 | /* 35 | * It would be so much simpler to just subclass Logger and override 36 | * the log(LogRecord) method, as the javadocs suggest, but that doesn't 37 | * work because Logger makes the decision about whether to log the message 38 | * or not before calling the log(LogRecord) method. Instead, we just 39 | * provide the few log methods we need here. 40 | */ 41 | 42 | public final class MailLogger { 43 | /** 44 | * For log messages. 45 | */ 46 | private final Logger logger; 47 | /** 48 | * For debug output. 49 | */ 50 | private final String prefix; 51 | /** 52 | * Produce debug output? 53 | */ 54 | private final boolean debug; 55 | /** 56 | * Stream for debug output. 57 | */ 58 | private final PrintStream out; 59 | 60 | /** 61 | * Construct a new MailLogger using the specified Logger name, 62 | * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream. 63 | * 64 | * @param name the Logger name 65 | * @param prefix the prefix for debug output, or null for none 66 | * @param debug if true, write to PrintStream 67 | * @param out the PrintStream to write to 68 | */ 69 | public MailLogger(String name, String prefix, boolean debug, 70 | PrintStream out) { 71 | logger = Logger.getLogger(name); 72 | this.prefix = prefix; 73 | this.debug = debug; 74 | this.out = out != null ? out : System.out; 75 | } 76 | 77 | /** 78 | * Construct a new MailLogger using the specified class' package 79 | * name as the Logger name, 80 | * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream. 81 | * 82 | * @param clazz the Logger name is the package name of this class 83 | * @param prefix the prefix for debug output, or null for none 84 | * @param debug if true, write to PrintStream 85 | * @param out the PrintStream to write to 86 | */ 87 | public MailLogger(Class clazz, String prefix, boolean debug, 88 | PrintStream out) { 89 | String name = packageOf(clazz); 90 | logger = Logger.getLogger(name); 91 | this.prefix = prefix; 92 | this.debug = debug; 93 | this.out = out != null ? out : System.out; 94 | } 95 | 96 | /** 97 | * Construct a new MailLogger using the specified class' package 98 | * name combined with the specified subname as the Logger name, 99 | * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream. 100 | * 101 | * @param clazz the Logger name is the package name of this class 102 | * @param subname the Logger name relative to this Logger name 103 | * @param prefix the prefix for debug output, or null for none 104 | * @param debug if true, write to PrintStream 105 | * @param out the PrintStream to write to 106 | */ 107 | public MailLogger(Class clazz, String subname, String prefix, boolean debug, 108 | PrintStream out) { 109 | String name = packageOf(clazz) + "." + subname; 110 | logger = Logger.getLogger(name); 111 | this.prefix = prefix; 112 | this.debug = debug; 113 | this.out = out != null ? out : System.out; 114 | } 115 | 116 | /** 117 | * Create a MailLogger that uses a Logger with the specified name 118 | * and prefix. The new MailLogger uses the same debug flag and 119 | * PrintStream as this MailLogger. 120 | * 121 | * @param name the Logger name 122 | * @param prefix the prefix for debug output, or null for none 123 | * @return a MailLogger for the given name and prefix. 124 | */ 125 | public MailLogger getLogger(String name, String prefix) { 126 | return new MailLogger(name, prefix, debug, out); 127 | } 128 | 129 | /** 130 | * Create a MailLogger using the specified class' package 131 | * name as the Logger name and the specified prefix. 132 | * The new MailLogger uses the same debug flag and 133 | * PrintStream as this MailLogger. 134 | * 135 | * @param clazz the Logger name is the package name of this class 136 | * @param prefix the prefix for debug output, or null for none 137 | * @return a MailLogger for the given name and prefix. 138 | */ 139 | public MailLogger getLogger(Class clazz, String prefix) { 140 | return new MailLogger(clazz, prefix, debug, out); 141 | } 142 | 143 | /** 144 | * Create a MailLogger that uses a Logger whose name is composed 145 | * of this MailLogger's name plus the specified sub-name, separated 146 | * by a dot. The new MailLogger uses the new prefix for debug output. 147 | * This is used primarily by the protocol trace code that wants a 148 | * different prefix (none). 149 | * 150 | * @param subname the Logger name relative to this Logger name 151 | * @param prefix the prefix for debug output, or null for none 152 | * @return a MailLogger for the given name and prefix. 153 | */ 154 | public MailLogger getSubLogger(String subname, String prefix) { 155 | return new MailLogger(logger.getName() + "." + subname, prefix, 156 | debug, out); 157 | } 158 | 159 | /** 160 | * Create a MailLogger that uses a Logger whose name is composed 161 | * of this MailLogger's name plus the specified sub-name, separated 162 | * by a dot. The new MailLogger uses the new prefix for debug output. 163 | * This is used primarily by the protocol trace code that wants a 164 | * different prefix (none). 165 | * 166 | * @param subname the Logger name relative to this Logger name 167 | * @param prefix the prefix for debug output, or null for none 168 | * @param debug the debug flag for the sub-logger 169 | * @return a MailLogger for the given name and prefix. 170 | */ 171 | public MailLogger getSubLogger(String subname, String prefix, 172 | boolean debug) { 173 | return new MailLogger(logger.getName() + "." + subname, prefix, 174 | debug, out); 175 | } 176 | 177 | /** 178 | * Log the message at the specified level. 179 | * @param level the log level. 180 | * @param msg the message. 181 | */ 182 | public void log(Level level, String msg) { 183 | ifDebugOut(msg); 184 | if (logger.isLoggable(level)) { 185 | final StackTraceElement frame = inferCaller(); 186 | logger.logp(level, frame.getClassName(), frame.getMethodName(), msg); 187 | } 188 | } 189 | 190 | /** 191 | * Log the message at the specified level. 192 | * @param level the log level. 193 | * @param msg the message. 194 | * @param param1 the additional parameter. 195 | */ 196 | public void log(Level level, String msg, Object param1) { 197 | if (debug) { 198 | msg = MessageFormat.format(msg, new Object[] { param1 }); 199 | debugOut(msg); 200 | } 201 | 202 | if (logger.isLoggable(level)) { 203 | final StackTraceElement frame = inferCaller(); 204 | logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, param1); 205 | } 206 | } 207 | 208 | /** 209 | * Log the message at the specified level. 210 | * @param level the log level. 211 | * @param msg the message. 212 | * @param params the message parameters. 213 | */ 214 | public void log(Level level, String msg, Object... params) { 215 | if (debug) { 216 | msg = MessageFormat.format(msg, params); 217 | debugOut(msg); 218 | } 219 | 220 | if (logger.isLoggable(level)) { 221 | final StackTraceElement frame = inferCaller(); 222 | logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, params); 223 | } 224 | } 225 | 226 | /** 227 | * Log the message at the specified level using a format string. 228 | * @param level the log level. 229 | * @param msg the message format string. 230 | * @param params the message parameters. 231 | * 232 | * @since JavaMail 1.5.4 233 | */ 234 | public void logf(Level level, String msg, Object... params) { 235 | msg = String.format(msg, params); 236 | ifDebugOut(msg); 237 | logger.log(level, msg); 238 | } 239 | 240 | /** 241 | * Log the message at the specified level. 242 | * @param level the log level. 243 | * @param msg the message. 244 | * @param thrown the throwable to log. 245 | */ 246 | public void log(Level level, String msg, Throwable thrown) { 247 | if (debug) { 248 | if (thrown != null) { 249 | debugOut(msg + ", THROW: "); 250 | thrown.printStackTrace(out); 251 | } else { 252 | debugOut(msg); 253 | } 254 | } 255 | 256 | if (logger.isLoggable(level)) { 257 | final StackTraceElement frame = inferCaller(); 258 | logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, thrown); 259 | } 260 | } 261 | 262 | /** 263 | * Log a message at the CONFIG level. 264 | * @param msg the message. 265 | */ 266 | public void config(String msg) { 267 | log(Level.CONFIG, msg); 268 | } 269 | 270 | /** 271 | * Log a message at the FINE level. 272 | * @param msg the message. 273 | */ 274 | public void fine(String msg) { 275 | log(Level.FINE, msg); 276 | } 277 | 278 | /** 279 | * Log a message at the FINER level. 280 | * @param msg the message. 281 | */ 282 | public void finer(String msg) { 283 | log(Level.FINER, msg); 284 | } 285 | 286 | /** 287 | * Log a message at the FINEST level. 288 | * @param msg the message. 289 | */ 290 | public void finest(String msg) { 291 | log(Level.FINEST, msg); 292 | } 293 | 294 | /** 295 | * If "debug" is set, or our embedded Logger is loggable at the 296 | * given level, return true. 297 | * @param level the log level. 298 | * @return true if loggable. 299 | */ 300 | public boolean isLoggable(Level level) { 301 | return debug || logger.isLoggable(level); 302 | } 303 | 304 | /** 305 | * Common code to conditionally log debug statements. 306 | * @param msg the message to log. 307 | */ 308 | private void ifDebugOut(String msg) { 309 | if (debug) 310 | debugOut(msg); 311 | } 312 | 313 | /** 314 | * Common formatting for debug output. 315 | * @param msg the message to log. 316 | */ 317 | private void debugOut(String msg) { 318 | if (prefix != null) 319 | out.println(prefix + ": " + msg); 320 | else 321 | out.println(msg); 322 | } 323 | 324 | /** 325 | * Return the package name of the class. 326 | * Sometimes there will be no Package object for the class, 327 | * e.g., if the class loader hasn't created one (see Class.getPackage()). 328 | * @param clazz the class source. 329 | * @return the package name or an empty string. 330 | */ 331 | private String packageOf(Class clazz) { 332 | Package p = clazz.getPackage(); 333 | if (p != null) 334 | return p.getName(); // hopefully the common case 335 | String cname = clazz.getName(); 336 | int i = cname.lastIndexOf('.'); 337 | if (i > 0) 338 | return cname.substring(0, i); 339 | // no package name, now what? 340 | return ""; 341 | } 342 | 343 | /** 344 | * A disadvantage of not being able to use Logger directly in Jakarta Mail 345 | * code is that the "source class" information that Logger guesses will 346 | * always refer to this class instead of our caller. This method 347 | * duplicates what Logger does to try to find *our* caller, so that 348 | * Logger doesn't have to do it (and get the wrong answer), and because 349 | * our caller is what's wanted. 350 | * @return StackTraceElement that logged the message. Treat as read-only. 351 | */ 352 | private StackTraceElement inferCaller() { 353 | // Get the stack trace. 354 | StackTraceElement stack[] = (new Throwable()).getStackTrace(); 355 | // First, search back to a method in the Logger class. 356 | int ix = 0; 357 | while (ix < stack.length) { 358 | StackTraceElement frame = stack[ix]; 359 | String cname = frame.getClassName(); 360 | if (isLoggerImplFrame(cname)) { 361 | break; 362 | } 363 | ix++; 364 | } 365 | // Now search for the first frame before the "Logger" class. 366 | while (ix < stack.length) { 367 | StackTraceElement frame = stack[ix]; 368 | String cname = frame.getClassName(); 369 | if (!isLoggerImplFrame(cname)) { 370 | // We've found the relevant frame. 371 | return frame; 372 | } 373 | ix++; 374 | } 375 | // We haven't found a suitable frame, so just punt. This is 376 | // OK as we are only committed to making a "best effort" here. 377 | return new StackTraceElement(MailLogger.class.getName(), "log", 378 | MailLogger.class.getName(), -1); 379 | } 380 | 381 | /** 382 | * Frames to ignore as part of the MailLogger to JUL bridge. 383 | * @param cname the class name. 384 | * @return true if the class name is part of the MailLogger bridge. 385 | */ 386 | private boolean isLoggerImplFrame(String cname) { 387 | return MailLogger.class.getName().equals(cname); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/jakarta/mail/internet/HeaderTokenizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.jakarta.mail.internet; 18 | 19 | import java.util.*; 20 | 21 | /** 22 | * This class tokenizes RFC822 and MIME headers into the basic 23 | * symbols specified by RFC822 and MIME.

24 | * 25 | * This class handles folded headers (ie headers with embedded 26 | * CRLF SPACE sequences). The folds are removed in the returned 27 | * tokens. 28 | * 29 | * @author John Mani 30 | * @author Bill Shannon 31 | */ 32 | 33 | public class HeaderTokenizer { 34 | 35 | /** 36 | * The Token class represents tokens returned by the 37 | * HeaderTokenizer. 38 | */ 39 | public static class Token { 40 | 41 | private int type; 42 | private String value; 43 | 44 | /** 45 | * Token type indicating an ATOM. 46 | */ 47 | public static final int ATOM = -1; 48 | 49 | /** 50 | * Token type indicating a quoted string. The value 51 | * field contains the string without the quotes. 52 | */ 53 | public static final int QUOTEDSTRING = -2; 54 | 55 | /** 56 | * Token type indicating a comment. The value field 57 | * contains the comment string without the comment 58 | * start and end symbols. 59 | */ 60 | public static final int COMMENT = -3; 61 | 62 | /** 63 | * Token type indicating end of input. 64 | */ 65 | public static final int EOF = -4; 66 | 67 | /** 68 | * Constructor. 69 | * @param type Token type 70 | * @param value Token value 71 | */ 72 | public Token(int type, String value) { 73 | this.type = type; 74 | this.value = value; 75 | } 76 | 77 | /** 78 | * Return the type of the token. If the token represents a 79 | * delimiter or a control character, the type is that character 80 | * itself, converted to an integer. Otherwise, it's value is 81 | * one of the following: 82 | *

    83 | *
  • ATOM A sequence of ASCII characters 84 | * delimited by either SPACE, CTL, "(", <"> or the 85 | * specified SPECIALS 86 | *
  • QUOTEDSTRING A sequence of ASCII characters 87 | * within quotes 88 | *
  • COMMENT A sequence of ASCII characters 89 | * within "(" and ")". 90 | *
  • EOF End of header 91 | *
92 | * 93 | * @return the token type 94 | */ 95 | public int getType() { 96 | return type; 97 | } 98 | 99 | /** 100 | * Returns the value of the token just read. When the current 101 | * token is a quoted string, this field contains the body of the 102 | * string, without the quotes. When the current token is a comment, 103 | * this field contains the body of the comment. 104 | * 105 | * @return token value 106 | */ 107 | public String getValue() { 108 | return value; 109 | } 110 | } 111 | 112 | private String string; // the string to be tokenized 113 | private boolean skipComments; // should comments be skipped ? 114 | private String delimiters; // delimiter string 115 | private int currentPos; // current parse position 116 | private int maxPos; // string length 117 | private int nextPos; // track start of next Token for next() 118 | private int peekPos; // track start of next Token for peek() 119 | 120 | /** 121 | * RFC822 specials 122 | */ 123 | public final static String RFC822 = "()<>@,;:\\\"\t .[]"; 124 | 125 | /** 126 | * MIME specials 127 | */ 128 | public final static String MIME = "()<>@,;:\\\"\t []/?="; 129 | 130 | // The EOF Token 131 | private final static Token EOFToken = new Token(Token.EOF, null); 132 | 133 | /** 134 | * Constructor that takes a rfc822 style header. 135 | * 136 | * @param header The rfc822 header to be tokenized 137 | * @param delimiters Set of delimiter characters 138 | * to be used to delimit ATOMS. These 139 | * are usually RFC822 or 140 | * MIME 141 | * @param skipComments If true, comments are skipped and 142 | * not returned as tokens 143 | */ 144 | public HeaderTokenizer(String header, String delimiters, 145 | boolean skipComments) { 146 | string = (header == null) ? "" : header; // paranoia ?! 147 | this.skipComments = skipComments; 148 | this.delimiters = delimiters; 149 | currentPos = nextPos = peekPos = 0; 150 | maxPos = string.length(); 151 | } 152 | 153 | /** 154 | * Constructor. Comments are ignored and not returned as tokens 155 | * 156 | * @param header The header that is tokenized 157 | * @param delimiters The delimiters to be used 158 | */ 159 | public HeaderTokenizer(String header, String delimiters) { 160 | this(header, delimiters, true); 161 | } 162 | 163 | /** 164 | * Constructor. The RFC822 defined delimiters - RFC822 - are 165 | * used to delimit ATOMS. Also comments are skipped and not 166 | * returned as tokens 167 | * 168 | * @param header the header string 169 | */ 170 | public HeaderTokenizer(String header) { 171 | this(header, RFC822); 172 | } 173 | 174 | /** 175 | * Parses the next token from this String.

176 | * 177 | * Clients sit in a loop calling next() to parse successive 178 | * tokens until an EOF Token is returned. 179 | * 180 | * @return the next Token 181 | * @exception ParseException if the parse fails 182 | */ 183 | public Token next() throws ParseException { 184 | return next('\0', false); 185 | } 186 | 187 | /** 188 | * Parses the next token from this String. 189 | * If endOfAtom is not NUL, the token extends until the 190 | * endOfAtom character is seen, or to the end of the header. 191 | * This method is useful when parsing headers that don't 192 | * obey the MIME specification, e.g., by failing to quote 193 | * parameter values that contain spaces. 194 | * 195 | * @param endOfAtom if not NUL, character marking end of token 196 | * @return the next Token 197 | * @exception ParseException if the parse fails 198 | * @since JavaMail 1.5 199 | */ 200 | public Token next(char endOfAtom) throws ParseException { 201 | return next(endOfAtom, false); 202 | } 203 | 204 | /** 205 | * Parses the next token from this String. 206 | * endOfAtom is handled as above. If keepEscapes is true, 207 | * any backslash escapes are preserved in the returned string. 208 | * This method is useful when parsing headers that don't 209 | * obey the MIME specification, e.g., by failing to escape 210 | * backslashes in the filename parameter. 211 | * 212 | * @param endOfAtom if not NUL, character marking end of token 213 | * @param keepEscapes keep all backslashes in returned string? 214 | * @return the next Token 215 | * @exception ParseException if the parse fails 216 | * @since JavaMail 1.5 217 | */ 218 | public Token next(char endOfAtom, boolean keepEscapes) 219 | throws ParseException { 220 | Token tk; 221 | 222 | currentPos = nextPos; // setup currentPos 223 | tk = getNext(endOfAtom, keepEscapes); 224 | nextPos = peekPos = currentPos; // update currentPos and peekPos 225 | return tk; 226 | } 227 | 228 | /** 229 | * Peek at the next token, without actually removing the token 230 | * from the parse stream. Invoking this method multiple times 231 | * will return successive tokens, until next() is 232 | * called.

233 | * 234 | * @return the next Token 235 | * @exception ParseException if the parse fails 236 | */ 237 | public Token peek() throws ParseException { 238 | Token tk; 239 | 240 | currentPos = peekPos; // setup currentPos 241 | tk = getNext('\0', false); 242 | peekPos = currentPos; // update peekPos 243 | return tk; 244 | } 245 | 246 | /** 247 | * Return the rest of the Header. 248 | * 249 | * @return String rest of header. null is returned if we are 250 | * already at end of header 251 | */ 252 | public String getRemainder() { 253 | if (nextPos >= string.length()) 254 | return null; 255 | return string.substring(nextPos); 256 | } 257 | 258 | /* 259 | * Return the next token starting from 'currentPos'. After the 260 | * parse, 'currentPos' is updated to point to the start of the 261 | * next token. 262 | */ 263 | private Token getNext(char endOfAtom, boolean keepEscapes) 264 | throws ParseException { 265 | // If we're already at end of string, return EOF 266 | if (currentPos >= maxPos) 267 | return EOFToken; 268 | 269 | // Skip white-space, position currentPos beyond the space 270 | if (skipWhiteSpace() == Token.EOF) 271 | return EOFToken; 272 | 273 | char c; 274 | int start; 275 | boolean filter = false; 276 | 277 | c = string.charAt(currentPos); 278 | 279 | // Check or Skip comments and position currentPos 280 | // beyond the comment 281 | while (c == '(') { 282 | // Parsing comment .. 283 | int nesting; 284 | for (start = ++currentPos, nesting = 1; 285 | nesting > 0 && currentPos < maxPos; 286 | currentPos++) { 287 | c = string.charAt(currentPos); 288 | if (c == '\\') { // Escape sequence 289 | currentPos++; // skip the escaped character 290 | filter = true; 291 | } else if (c == '\r') 292 | filter = true; 293 | else if (c == '(') 294 | nesting++; 295 | else if (c == ')') 296 | nesting--; 297 | } 298 | if (nesting != 0) 299 | throw new ParseException("Unbalanced comments"); 300 | 301 | if (!skipComments) { 302 | // Return the comment, if we are asked to. 303 | // Note that the comment start & end markers are ignored. 304 | String s; 305 | if (filter) // need to go thru the token again. 306 | s = filterToken(string, start, currentPos-1, keepEscapes); 307 | else 308 | s = string.substring(start,currentPos-1); 309 | 310 | return new Token(Token.COMMENT, s); 311 | } 312 | 313 | // Skip any whitespace after the comment. 314 | if (skipWhiteSpace() == Token.EOF) 315 | return EOFToken; 316 | c = string.charAt(currentPos); 317 | } 318 | 319 | // Check for quoted-string and position currentPos 320 | // beyond the terminating quote 321 | if (c == '"') { 322 | currentPos++; // skip initial quote 323 | return collectString('"', keepEscapes); 324 | } 325 | 326 | // Check for SPECIAL or CTL 327 | if (c < 040 || c >= 0177 || delimiters.indexOf(c) >= 0) { 328 | if (endOfAtom > 0 && c != endOfAtom) { 329 | // not expecting a special character here, 330 | // pretend it's a quoted string 331 | return collectString(endOfAtom, keepEscapes); 332 | } 333 | currentPos++; // re-position currentPos 334 | char ch[] = new char[1]; 335 | ch[0] = c; 336 | return new Token((int)c, new String(ch)); 337 | } 338 | 339 | // Check for ATOM 340 | for (start = currentPos; currentPos < maxPos; currentPos++) { 341 | c = string.charAt(currentPos); 342 | // ATOM is delimited by either SPACE, CTL, "(", <"> 343 | // or the specified SPECIALS 344 | if (c < 040 || c >= 0177 || c == '(' || c == ' ' || 345 | c == '"' || delimiters.indexOf(c) >= 0) { 346 | if (endOfAtom > 0 && c != endOfAtom) { 347 | // not the expected atom after all; 348 | // back up and pretend it's a quoted string 349 | currentPos = start; 350 | return collectString(endOfAtom, keepEscapes); 351 | } 352 | break; 353 | } 354 | } 355 | return new Token(Token.ATOM, string.substring(start, currentPos)); 356 | } 357 | 358 | private Token collectString(char eos, boolean keepEscapes) 359 | throws ParseException { 360 | int start; 361 | boolean filter = false; 362 | for (start = currentPos; currentPos < maxPos; currentPos++) { 363 | char c = string.charAt(currentPos); 364 | if (c == '\\') { // Escape sequence 365 | currentPos++; 366 | filter = true; 367 | } else if (c == '\r') 368 | filter = true; 369 | else if (c == eos) { 370 | currentPos++; 371 | String s; 372 | 373 | if (filter) 374 | s = filterToken(string, start, currentPos-1, keepEscapes); 375 | else 376 | s = string.substring(start, currentPos-1); 377 | 378 | if (c != '"') { // not a real quoted string 379 | s = trimWhiteSpace(s); 380 | currentPos--; // back up before the eos char 381 | } 382 | 383 | return new Token(Token.QUOTEDSTRING, s); 384 | } 385 | } 386 | 387 | // ran off the end of the string 388 | 389 | // if we're looking for a matching quote, that's an error 390 | if (eos == '"') 391 | throw new ParseException("Unbalanced quoted string"); 392 | 393 | // otherwise, just return whatever's left 394 | String s; 395 | if (filter) 396 | s = filterToken(string, start, currentPos, keepEscapes); 397 | else 398 | s = string.substring(start, currentPos); 399 | s = trimWhiteSpace(s); 400 | return new Token(Token.QUOTEDSTRING, s); 401 | } 402 | 403 | // Skip SPACE, HT, CR and NL 404 | private int skipWhiteSpace() { 405 | char c; 406 | for (; currentPos < maxPos; currentPos++) 407 | if (((c = string.charAt(currentPos)) != ' ') && 408 | (c != '\t') && (c != '\r') && (c != '\n')) 409 | return currentPos; 410 | return Token.EOF; 411 | } 412 | 413 | // Trim SPACE, HT, CR and NL from end of string 414 | private static String trimWhiteSpace(String s) { 415 | char c; 416 | int i; 417 | for (i = s.length() - 1; i >= 0; i--) { 418 | if (((c = s.charAt(i)) != ' ') && 419 | (c != '\t') && (c != '\r') && (c != '\n')) 420 | break; 421 | } 422 | if (i <= 0) 423 | return ""; 424 | else 425 | return s.substring(0, i + 1); 426 | } 427 | 428 | /* Process escape sequences and embedded LWSPs from a comment or 429 | * quoted string. 430 | */ 431 | private static String filterToken(String s, int start, int end, 432 | boolean keepEscapes) { 433 | StringBuilder sb = new StringBuilder(); 434 | char c; 435 | boolean gotEscape = false; 436 | boolean gotCR = false; 437 | 438 | for (int i = start; i < end; i++) { 439 | c = s.charAt(i); 440 | if (c == '\n' && gotCR) { 441 | // This LF is part of an unescaped 442 | // CRLF sequence (i.e, LWSP). Skip it. 443 | gotCR = false; 444 | continue; 445 | } 446 | 447 | gotCR = false; 448 | if (!gotEscape) { 449 | // Previous character was NOT '\' 450 | if (c == '\\') // skip this character 451 | gotEscape = true; 452 | else if (c == '\r') // skip this character 453 | gotCR = true; 454 | else // append this character 455 | sb.append(c); 456 | } else { 457 | // Previous character was '\'. So no need to 458 | // bother with any special processing, just 459 | // append this character. If keepEscapes is 460 | // set, keep the backslash. IE6 fails to escape 461 | // backslashes in quoted strings in HTTP headers, 462 | // e.g., in the filename parameter. 463 | if (keepEscapes) 464 | sb.append('\\'); 465 | sb.append(c); 466 | gotEscape = false; 467 | } 468 | } 469 | return sb.toString(); 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /properties-list2.txt: -------------------------------------------------------------------------------- 1 | This second partial list explains some of the properties we recognize or ignore 2 | 3 | (source: https://github.com/JoshData/convert-outlook-msg-file/blob/master/outlookmsgfile.py) 4 | 5 | # from mapitags.h via https://github.com/mvz/email-outlook-message-perl/blob/master/mapitags.h 6 | property_tags = { 7 | 0x01: ('ACKNOWLEDGEMENT_MODE', 'I4'), 8 | 0x02: ('ALTERNATE_RECIPIENT_ALLOWED', 'BOOLEAN'), 9 | 0x03: ('AUTHORIZING_USERS', 'BINARY'), 10 | # Comment on an automatically forwarded message 11 | 0x04: ('AUTO_FORWARD_COMMENT', 'STRING'), 12 | # Whether a message has been automatically forwarded 13 | 0x05: ('AUTO_FORWARDED', 'BOOLEAN'), 14 | 0x06: ('CONTENT_CONFIDENTIALITY_ALGORITHM_ID', 'BINARY'), 15 | 0x07: ('CONTENT_CORRELATOR', 'BINARY'), 16 | 0x08: ('CONTENT_IDENTIFIER', 'STRING'), 17 | # MIME content length 18 | 0x09: ('CONTENT_LENGTH', 'I4'), 19 | 0x0A: ('CONTENT_RETURN_REQUESTED', 'BOOLEAN'), 20 | 0x0B: ('CONVERSATION_KEY', 'BINARY'), 21 | 0x0C: ('CONVERSION_EITS', 'BINARY'), 22 | 0x0D: ('CONVERSION_WITH_LOSS_PROHIBITED', 'BOOLEAN'), 23 | 0x0E: ('CONVERTED_EITS', 'BINARY'), 24 | # Time to deliver for delayed delivery messages 25 | 0x0F: ('DEFERRED_DELIVERY_TIME', 'SYSTIME'), 26 | 0x10: ('DELIVER_TIME', 'SYSTIME'), 27 | # Reason a message was discarded 28 | 0x11: ('DISCARD_REASON', 'I4'), 29 | 0x12: ('DISCLOSURE_OF_RECIPIENTS', 'BOOLEAN'), 30 | 0x13: ('DL_EXPANSION_HISTORY', 'BINARY'), 31 | 0x14: ('DL_EXPANSION_PROHIBITED', 'BOOLEAN'), 32 | 0x15: ('EXPIRY_TIME', 'SYSTIME'), 33 | 0x16: ('IMPLICIT_CONVERSION_PROHIBITED', 'BOOLEAN'), 34 | # Message importance 35 | 0x17: ('IMPORTANCE', 'I4'), 36 | 0x18: ('IPM_ID', 'BINARY'), 37 | 0x19: ('LATEST_DELIVERY_TIME', 'SYSTIME'), 38 | 0x1A: ('MESSAGE_CLASS', 'STRING'), 39 | 0x1B: ('MESSAGE_DELIVERY_ID', 'BINARY'), 40 | 0x1E: ('MESSAGE_SECURITY_LABEL', 'BINARY'), 41 | 0x1F: ('OBSOLETED_IPMS', 'BINARY'), 42 | # Person a message was originally for 43 | 0x20: ('ORIGINALLY_INTENDED_RECIPIENT_NAME', 'BINARY'), 44 | 0x21: ('ORIGINAL_EITS', 'BINARY'), 45 | 0x22: ('ORIGINATOR_CERTIFICATE', 'BINARY'), 46 | 0x23: ('ORIGINATOR_DELIVERY_REPORT_REQUESTED', 'BOOLEAN'), 47 | # Address of the message sender 48 | 0x24: ('ORIGINATOR_RETURN_ADDRESS', 'BINARY'), 49 | 0x25: ('PARENT_KEY', 'BINARY'), 50 | 0x26: ('PRIORITY', 'I4'), 51 | 0x27: ('ORIGIN_CHECK', 'BINARY'), 52 | 0x28: ('PROOF_OF_SUBMISSION_REQUESTED', 'BOOLEAN'), 53 | # Whether a read receipt is desired 54 | 0x29: ('READ_RECEIPT_REQUESTED', 'BOOLEAN'), 55 | # Time a message was received 56 | 0x2A: ('RECEIPT_TIME', 'SYSTIME'), 57 | 0x2B: ('RECIPIENT_REASSIGNMENT_PROHIBITED', 'BOOLEAN'), 58 | 0x2C: ('REDIRECTION_HISTORY', 'BINARY'), 59 | 0x2D: ('RELATED_IPMS', 'BINARY'), 60 | # Sensitivity of the original message 61 | 0x2E: ('ORIGINAL_SENSITIVITY', 'I4'), 62 | 0x2F: ('LANGUAGES', 'STRING'), 63 | 0x30: ('REPLY_TIME', 'SYSTIME'), 64 | 0x31: ('REPORT_TAG', 'BINARY'), 65 | 0x32: ('REPORT_TIME', 'SYSTIME'), 66 | 0x33: ('RETURNED_IPM', 'BOOLEAN'), 67 | 0x34: ('SECURITY', 'I4'), 68 | 0x35: ('INCOMPLETE_COPY', 'BOOLEAN'), 69 | 0x36: ('SENSITIVITY', 'I4'), 70 | # The message subject 71 | 0x37: ('SUBJECT', 'STRING'), 72 | 0x38: ('SUBJECT_IPM', 'BINARY'), 73 | 0x39: ('CLIENT_SUBMIT_TIME', 'SYSTIME'), 74 | 0x3A: ('REPORT_NAME', 'STRING'), 75 | 0x3B: ('SENT_REPRESENTING_SEARCH_KEY', 'BINARY'), 76 | 0x3C: ('X400_CONTENT_TYPE', 'BINARY'), 77 | 0x3D: ('SUBJECT_PREFIX', 'STRING'), 78 | 0x3E: ('NON_RECEIPT_REASON', 'I4'), 79 | 0x3F: ('RECEIVED_BY_ENTRYID', 'BINARY'), 80 | # Received by: entry 81 | 0x40: ('RECEIVED_BY_NAME', 'STRING'), 82 | 0x41: ('SENT_REPRESENTING_ENTRYID', 'BINARY'), 83 | 0x42: ('SENT_REPRESENTING_NAME', 'STRING'), 84 | 0x43: ('RCVD_REPRESENTING_ENTRYID', 'BINARY'), 85 | 0x44: ('RCVD_REPRESENTING_NAME', 'STRING'), 86 | 0x45: ('REPORT_ENTRYID', 'BINARY'), 87 | 0x46: ('READ_RECEIPT_ENTRYID', 'BINARY'), 88 | 0x47: ('MESSAGE_SUBMISSION_ID', 'BINARY'), 89 | 0x48: ('PROVIDER_SUBMIT_TIME', 'SYSTIME'), 90 | # Subject of the original message 91 | 0x49: ('ORIGINAL_SUBJECT', 'STRING'), 92 | 0x4A: ('DISC_VAL', 'BOOLEAN'), 93 | 0x4B: ('ORIG_MESSAGE_CLASS', 'STRING'), 94 | 0x4C: ('ORIGINAL_AUTHOR_ENTRYID', 'BINARY'), 95 | # Author of the original message 96 | 0x4D: ('ORIGINAL_AUTHOR_NAME', 'STRING'), 97 | # Time the original message was submitted 98 | 0x4E: ('ORIGINAL_SUBMIT_TIME', 'SYSTIME'), 99 | 0x4F: ('REPLY_RECIPIENT_ENTRIES', 'BINARY'), 100 | 0x50: ('REPLY_RECIPIENT_NAMES', 'STRING'), 101 | 0x51: ('RECEIVED_BY_SEARCH_KEY', 'BINARY'), 102 | 0x52: ('RCVD_REPRESENTING_SEARCH_KEY', 'BINARY'), 103 | 0x53: ('READ_RECEIPT_SEARCH_KEY', 'BINARY'), 104 | 0x54: ('REPORT_SEARCH_KEY', 'BINARY'), 105 | 0x55: ('ORIGINAL_DELIVERY_TIME', 'SYSTIME'), 106 | 0x56: ('ORIGINAL_AUTHOR_SEARCH_KEY', 'BINARY'), 107 | 0x57: ('MESSAGE_TO_ME', 'BOOLEAN'), 108 | 0x58: ('MESSAGE_CC_ME', 'BOOLEAN'), 109 | 0x59: ('MESSAGE_RECIP_ME', 'BOOLEAN'), 110 | # Sender of the original message 111 | 0x5A: ('ORIGINAL_SENDER_NAME', 'STRING'), 112 | 0x5B: ('ORIGINAL_SENDER_ENTRYID', 'BINARY'), 113 | 0x5C: ('ORIGINAL_SENDER_SEARCH_KEY', 'BINARY'), 114 | 0x5D: ('ORIGINAL_SENT_REPRESENTING_NAME', 'STRING'), 115 | 0x5E: ('ORIGINAL_SENT_REPRESENTING_ENTRYID', 'BINARY'), 116 | 0x5F: ('ORIGINAL_SENT_REPRESENTING_SEARCH_KEY', 'BINARY'), 117 | 0x60: ('START_DATE', 'SYSTIME'), 118 | 0x61: ('END_DATE', 'SYSTIME'), 119 | 0x62: ('OWNER_APPT_ID', 'I4'), 120 | # Whether a response to the message is desired 121 | 0x63: ('RESPONSE_REQUESTED', 'BOOLEAN'), 122 | 0x64: ('SENT_REPRESENTING_ADDRTYPE', 'STRING'), 123 | 0x65: ('SENT_REPRESENTING_EMAIL_ADDRESS', 'STRING'), 124 | 0x66: ('ORIGINAL_SENDER_ADDRTYPE', 'STRING'), 125 | # Email of the original message sender 126 | 0x67: ('ORIGINAL_SENDER_EMAIL_ADDRESS', 'STRING'), 127 | 0x68: ('ORIGINAL_SENT_REPRESENTING_ADDRTYPE', 'STRING'), 128 | 0x69: ('ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS', 'STRING'), 129 | 0x70: ('CONVERSATION_TOPIC', 'STRING'), 130 | 0x71: ('CONVERSATION_INDEX', 'BINARY'), 131 | 0x72: ('ORIGINAL_DISPLAY_BCC', 'STRING'), 132 | 0x73: ('ORIGINAL_DISPLAY_CC', 'STRING'), 133 | 0x74: ('ORIGINAL_DISPLAY_TO', 'STRING'), 134 | 0x75: ('RECEIVED_BY_ADDRTYPE', 'STRING'), 135 | 0x76: ('RECEIVED_BY_EMAIL_ADDRESS', 'STRING'), 136 | 0x77: ('RCVD_REPRESENTING_ADDRTYPE', 'STRING'), 137 | 0x78: ('RCVD_REPRESENTING_EMAIL_ADDRESS', 'STRING'), 138 | 0x79: ('ORIGINAL_AUTHOR_ADDRTYPE', 'STRING'), 139 | 0x7A: ('ORIGINAL_AUTHOR_EMAIL_ADDRESS', 'STRING'), 140 | 0x7B: ('ORIGINALLY_INTENDED_RECIP_ADDRTYPE', 'STRING'), 141 | 0x7C: ('ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS', 'STRING'), 142 | 0x7D: ('TRANSPORT_MESSAGE_HEADERS', 'STRING'), 143 | 0x7E: ('DELEGATION', 'BINARY'), 144 | 0x7F: ('TNEF_CORRELATION_KEY', 'BINARY'), 145 | 0x1000: ('BODY', 'STRING'), 146 | 0x1001: ('REPORT_TEXT', 'STRING'), 147 | 0x1002: ('ORIGINATOR_AND_DL_EXPANSION_HISTORY', 'BINARY'), 148 | 0x1003: ('REPORTING_DL_NAME', 'BINARY'), 149 | 0x1004: ('REPORTING_MTA_CERTIFICATE', 'BINARY'), 150 | 0x1006: ('RTF_SYNC_BODY_CRC', 'I4'), 151 | 0x1007: ('RTF_SYNC_BODY_COUNT', 'I4'), 152 | 0x1008: ('RTF_SYNC_BODY_TAG', 'STRING'), 153 | 0x1009: ('RTF_COMPRESSED', 'BINARY'), 154 | 0x1010: ('RTF_SYNC_PREFIX_COUNT', 'I4'), 155 | 0x1011: ('RTF_SYNC_TRAILING_COUNT', 'I4'), 156 | 0x1012: ('ORIGINALLY_INTENDED_RECIP_ENTRYID', 'BINARY'), 157 | 0x0C00: ('CONTENT_INTEGRITY_CHECK', 'BINARY'), 158 | 0x0C01: ('EXPLICIT_CONVERSION', 'I4'), 159 | 0x0C02: ('IPM_RETURN_REQUESTED', 'BOOLEAN'), 160 | 0x0C03: ('MESSAGE_TOKEN', 'BINARY'), 161 | 0x0C04: ('NDR_REASON_CODE', 'I4'), 162 | 0x0C05: ('NDR_DIAG_CODE', 'I4'), 163 | 0x0C06: ('NON_RECEIPT_NOTIFICATION_REQUESTED', 'BOOLEAN'), 164 | 0x0C07: ('DELIVERY_POINT', 'I4'), 165 | 0x0C08: ('ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED', 'BOOLEAN'), 166 | 0x0C09: ('ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT', 'BINARY'), 167 | 0x0C0A: ('PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY', 'BOOLEAN'), 168 | 0x0C0B: ('PHYSICAL_DELIVERY_MODE', 'I4'), 169 | 0x0C0C: ('PHYSICAL_DELIVERY_REPORT_REQUEST', 'I4'), 170 | 0x0C0D: ('PHYSICAL_FORWARDING_ADDRESS', 'BINARY'), 171 | 0x0C0E: ('PHYSICAL_FORWARDING_ADDRESS_REQUESTED', 'BOOLEAN'), 172 | 0x0C0F: ('PHYSICAL_FORWARDING_PROHIBITED', 'BOOLEAN'), 173 | 0x0C10: ('PHYSICAL_RENDITION_ATTRIBUTES', 'BINARY'), 174 | 0x0C11: ('PROOF_OF_DELIVERY', 'BINARY'), 175 | 0x0C12: ('PROOF_OF_DELIVERY_REQUESTED', 'BOOLEAN'), 176 | 0x0C13: ('RECIPIENT_CERTIFICATE', 'BINARY'), 177 | 0x0C14: ('RECIPIENT_NUMBER_FOR_ADVICE', 'STRING'), 178 | 0x0C15: ('RECIPIENT_TYPE', 'I4'), 179 | 0x0C16: ('REGISTERED_MAIL_TYPE', 'I4'), 180 | 0x0C17: ('REPLY_REQUESTED', 'BOOLEAN'), 181 | 0x0C18: ('REQUESTED_DELIVERY_METHOD', 'I4'), 182 | 0x0C19: ('SENDER_ENTRYID', 'BINARY'), 183 | 0x0C1A: ('SENDER_NAME', 'STRING'), 184 | 0x0C1B: ('SUPPLEMENTARY_INFO', 'STRING'), 185 | 0x0C1C: ('TYPE_OF_MTS_USER', 'I4'), 186 | 0x0C1D: ('SENDER_SEARCH_KEY', 'BINARY'), 187 | 0x0C1E: ('SENDER_ADDRTYPE', 'STRING'), 188 | 0x0C1F: ('SENDER_EMAIL_ADDRESS', 'STRING'), 189 | 0x0E00: ('CURRENT_VERSION', 'I8'), 190 | 0x0E01: ('DELETE_AFTER_SUBMIT', 'BOOLEAN'), 191 | 0x0E02: ('DISPLAY_BCC', 'STRING'), 192 | 0x0E03: ('DISPLAY_CC', 'STRING'), 193 | 0x0E04: ('DISPLAY_TO', 'STRING'), 194 | 0x0E05: ('PARENT_DISPLAY', 'STRING'), 195 | 0x0E06: ('MESSAGE_DELIVERY_TIME', 'SYSTIME'), 196 | 0x0E07: ('MESSAGE_FLAGS', 'I4'), 197 | 0x0E08: ('MESSAGE_SIZE', 'I4'), 198 | 0x0E09: ('PARENT_ENTRYID', 'BINARY'), 199 | 0x0E0A: ('SENTMAIL_ENTRYID', 'BINARY'), 200 | 0x0E0C: ('CORRELATE', 'BOOLEAN'), 201 | 0x0E0D: ('CORRELATE_MTSID', 'BINARY'), 202 | 0x0E0E: ('DISCRETE_VALUES', 'BOOLEAN'), 203 | 0x0E0F: ('RESPONSIBILITY', 'BOOLEAN'), 204 | 0x0E10: ('SPOOLER_STATUS', 'I4'), 205 | 0x0E11: ('TRANSPORT_STATUS', 'I4'), 206 | 0x0E12: ('MESSAGE_RECIPIENTS', 'OBJECT'), 207 | 0x0E13: ('MESSAGE_ATTACHMENTS', 'OBJECT'), 208 | 0x0E14: ('SUBMIT_FLAGS', 'I4'), 209 | 0x0E15: ('RECIPIENT_STATUS', 'I4'), 210 | 0x0E16: ('TRANSPORT_KEY', 'I4'), 211 | 0x0E17: ('MSG_STATUS', 'I4'), 212 | 0x0E18: ('MESSAGE_DOWNLOAD_TIME', 'I4'), 213 | 0x0E19: ('CREATION_VERSION', 'I8'), 214 | 0x0E1A: ('MODIFY_VERSION', 'I8'), 215 | 0x0E1B: ('HASATTACH', 'BOOLEAN'), 216 | 0x0E1D: ('NORMALIZED_SUBJECT', 'STRING'), 217 | 0x0E1F: ('RTF_IN_SYNC', 'BOOLEAN'), 218 | 0x0E20: ('ATTACH_SIZE', 'I4'), 219 | 0x0E21: ('ATTACH_NUM', 'I4'), 220 | 0x0E22: ('PREPROCESS', 'BOOLEAN'), 221 | 0x0E25: ('ORIGINATING_MTA_CERTIFICATE', 'BINARY'), 222 | 0x0E26: ('PROOF_OF_SUBMISSION', 'BINARY'), 223 | # A unique identifier for editing the properties of a MAPI object 224 | 0x0FFF: ('ENTRYID', 'BINARY'), 225 | # The type of an object 226 | 0x0FFE: ('OBJECT_TYPE', 'I4'), 227 | 0x0FFD: ('ICON', 'BINARY'), 228 | 0x0FFC: ('MINI_ICON', 'BINARY'), 229 | 0x0FFB: ('STORE_ENTRYID', 'BINARY'), 230 | 0x0FFA: ('STORE_RECORD_KEY', 'BINARY'), 231 | # Binary identifer for an individual object 232 | 0x0FF9: ('RECORD_KEY', 'BINARY'), 233 | 0x0FF8: ('MAPPING_SIGNATURE', 'BINARY'), 234 | 0x0FF7: ('ACCESS_LEVEL', 'I4'), 235 | # The primary key of a column in a table 236 | 0x0FF6: ('INSTANCE_KEY', 'BINARY'), 237 | 0x0FF5: ('ROW_TYPE', 'I4'), 238 | 0x0FF4: ('ACCESS', 'I4'), 239 | 0x3000: ('ROWID', 'I4'), 240 | # The name to display for a given MAPI object 241 | 0x3001: ('DISPLAY_NAME', 'STRING'), 242 | 0x3002: ('ADDRTYPE', 'STRING'), 243 | # An email address 244 | 0x3003: ('EMAIL_ADDRESS', 'STRING'), 245 | # A comment field 246 | 0x3004: ('COMMENT', 'STRING'), 247 | 0x3005: ('DEPTH', 'I4'), 248 | # Provider-defined display name for a service provider 249 | 0x3006: ('PROVIDER_DISPLAY', 'STRING'), 250 | # The time an object was created 251 | 0x3007: ('CREATION_TIME', 'SYSTIME'), 252 | # The time an object was last modified 253 | 0x3008: ('LAST_MODIFICATION_TIME', 'SYSTIME'), 254 | # Flags describing a service provider, message service, or status object 255 | 0x3009: ('RESOURCE_FLAGS', 'I4'), 256 | # The name of a provider dll, minus any "32" suffix and ".dll" 257 | 0x300A: ('PROVIDER_DLL_NAME', 'STRING'), 258 | 0x300B: ('SEARCH_KEY', 'BINARY'), 259 | 0x300C: ('PROVIDER_UID', 'BINARY'), 260 | 0x300D: ('PROVIDER_ORDINAL', 'I4'), 261 | 0x3301: ('FORM_VERSION', 'STRING'), 262 | 0x3302: ('FORM_CLSID', 'CLSID'), 263 | 0x3303: ('FORM_CONTACT_NAME', 'STRING'), 264 | 0x3304: ('FORM_CATEGORY', 'STRING'), 265 | 0x3305: ('FORM_CATEGORY_SUB', 'STRING'), 266 | 0x3306: ('FORM_HOST_MAP', 'MV_LONG'), 267 | 0x3307: ('FORM_HIDDEN', 'BOOLEAN'), 268 | 0x3308: ('FORM_DESIGNER_NAME', 'STRING'), 269 | 0x3309: ('FORM_DESIGNER_GUID', 'CLSID'), 270 | 0x330A: ('FORM_MESSAGE_BEHAVIOR', 'I4'), 271 | # Is this row the default message store? 272 | 0x3400: ('DEFAULT_STORE', 'BOOLEAN'), 273 | 0x340D: ('STORE_SUPPORT_MASK', 'I4'), 274 | 0x340E: ('STORE_STATE', 'I4'), 275 | 0x3410: ('IPM_SUBTREE_SEARCH_KEY', 'BINARY'), 276 | 0x3411: ('IPM_OUTBOX_SEARCH_KEY', 'BINARY'), 277 | 0x3412: ('IPM_WASTEBASKET_SEARCH_KEY', 'BINARY'), 278 | 0x3413: ('IPM_SENTMAIL_SEARCH_KEY', 'BINARY'), 279 | # Provder-defined message store type 280 | 0x3414: ('MDB_PROVIDER', 'BINARY'), 281 | 0x3415: ('RECEIVE_FOLDER_SETTINGS', 'OBJECT'), 282 | 0x35DF: ('VALID_FOLDER_MASK', 'I4'), 283 | 0x35E0: ('IPM_SUBTREE_ENTRYID', 'BINARY'), 284 | 0x35E2: ('IPM_OUTBOX_ENTRYID', 'BINARY'), 285 | 0x35E3: ('IPM_WASTEBASKET_ENTRYID', 'BINARY'), 286 | 0x35E4: ('IPM_SENTMAIL_ENTRYID', 'BINARY'), 287 | 0x35E5: ('VIEWS_ENTRYID', 'BINARY'), 288 | 0x35E6: ('COMMON_VIEWS_ENTRYID', 'BINARY'), 289 | 0x35E7: ('FINDER_ENTRYID', 'BINARY'), 290 | 0x3600: ('CONTAINER_FLAGS', 'I4'), 291 | 0x3601: ('FOLDER_TYPE', 'I4'), 292 | 0x3602: ('CONTENT_COUNT', 'I4'), 293 | 0x3603: ('CONTENT_UNREAD', 'I4'), 294 | 0x3604: ('CREATE_TEMPLATES', 'OBJECT'), 295 | 0x3605: ('DETAILS_TABLE', 'OBJECT'), 296 | 0x3607: ('SEARCH', 'OBJECT'), 297 | 0x3609: ('SELECTABLE', 'BOOLEAN'), 298 | 0x360A: ('SUBFOLDERS', 'BOOLEAN'), 299 | 0x360B: ('STATUS', 'I4'), 300 | 0x360C: ('ANR', 'STRING'), 301 | 0x360D: ('CONTENTS_SORT_ORDER', 'MV_LONG'), 302 | 0x360E: ('CONTAINER_HIERARCHY', 'OBJECT'), 303 | 0x360F: ('CONTAINER_CONTENTS', 'OBJECT'), 304 | 0x3610: ('FOLDER_ASSOCIATED_CONTENTS', 'OBJECT'), 305 | 0x3611: ('DEF_CREATE_DL', 'BINARY'), 306 | 0x3612: ('DEF_CREATE_MAILUSER', 'BINARY'), 307 | 0x3613: ('CONTAINER_CLASS', 'STRING'), 308 | 0x3614: ('CONTAINER_MODIFY_VERSION', 'I8'), 309 | 0x3615: ('AB_PROVIDER_ID', 'BINARY'), 310 | 0x3616: ('DEFAULT_VIEW_ENTRYID', 'BINARY'), 311 | 0x3617: ('ASSOC_CONTENT_COUNT', 'I4'), 312 | 0x3700: ('ATTACHMENT_X400_PARAMETERS', 'BINARY'), 313 | 0x3701: ('ATTACH_DATA_OBJ', 'OBJECT'), 314 | 0x3701: ('ATTACH_DATA_BIN', 'BINARY'), 315 | 0x3702: ('ATTACH_ENCODING', 'BINARY'), 316 | 0x3703: ('ATTACH_EXTENSION', 'STRING'), 317 | 0x3704: ('ATTACH_FILENAME', 'STRING'), 318 | 0x3705: ('ATTACH_METHOD', 'I4'), 319 | 0x3707: ('ATTACH_LONG_FILENAME', 'STRING'), 320 | 0x3708: ('ATTACH_PATHNAME', 'STRING'), 321 | 0x370A: ('ATTACH_TAG', 'BINARY'), 322 | 0x370B: ('RENDERING_POSITION', 'I4'), 323 | 0x370C: ('ATTACH_TRANSPORT_NAME', 'STRING'), 324 | 0x370D: ('ATTACH_LONG_PATHNAME', 'STRING'), 325 | 0x370E: ('ATTACH_MIME_TAG', 'STRING'), 326 | 0x370F: ('ATTACH_ADDITIONAL_INFO', 'BINARY'), 327 | 0x3900: ('DISPLAY_TYPE', 'I4'), 328 | 0x3902: ('TEMPLATEID', 'BINARY'), 329 | 0x3904: ('PRIMARY_CAPABILITY', 'BINARY'), 330 | 0x39FF: ('7BIT_DISPLAY_NAME', 'STRING'), 331 | 0x3A00: ('ACCOUNT', 'STRING'), 332 | 0x3A01: ('ALTERNATE_RECIPIENT', 'BINARY'), 333 | 0x3A02: ('CALLBACK_TELEPHONE_NUMBER', 'STRING'), 334 | 0x3A03: ('CONVERSION_PROHIBITED', 'BOOLEAN'), 335 | 0x3A04: ('DISCLOSE_RECIPIENTS', 'BOOLEAN'), 336 | 0x3A05: ('GENERATION', 'STRING'), 337 | 0x3A06: ('GIVEN_NAME', 'STRING'), 338 | 0x3A07: ('GOVERNMENT_ID_NUMBER', 'STRING'), 339 | 0x3A08: ('BUSINESS_TELEPHONE_NUMBER', 'STRING'), 340 | 0x3A09: ('HOME_TELEPHONE_NUMBER', 'STRING'), 341 | 0x3A0A: ('INITIALS', 'STRING'), 342 | 0x3A0B: ('KEYWORD', 'STRING'), 343 | 0x3A0C: ('LANGUAGE', 'STRING'), 344 | 0x3A0D: ('LOCATION', 'STRING'), 345 | 0x3A0E: ('MAIL_PERMISSION', 'BOOLEAN'), 346 | 0x3A0F: ('MHS_COMMON_NAME', 'STRING'), 347 | 0x3A10: ('ORGANIZATIONAL_ID_NUMBER', 'STRING'), 348 | 0x3A11: ('SURNAME', 'STRING'), 349 | 0x3A12: ('ORIGINAL_ENTRYID', 'BINARY'), 350 | 0x3A13: ('ORIGINAL_DISPLAY_NAME', 'STRING'), 351 | 0x3A14: ('ORIGINAL_SEARCH_KEY', 'BINARY'), 352 | 0x3A15: ('POSTAL_ADDRESS', 'STRING'), 353 | 0x3A16: ('COMPANY_NAME', 'STRING'), 354 | 0x3A17: ('TITLE', 'STRING'), 355 | 0x3A18: ('DEPARTMENT_NAME', 'STRING'), 356 | 0x3A19: ('OFFICE_LOCATION', 'STRING'), 357 | 0x3A1A: ('PRIMARY_TELEPHONE_NUMBER', 'STRING'), 358 | 0x3A1B: ('BUSINESS2_TELEPHONE_NUMBER', 'STRING'), 359 | 0x3A1C: ('MOBILE_TELEPHONE_NUMBER', 'STRING'), 360 | 0x3A1D: ('RADIO_TELEPHONE_NUMBER', 'STRING'), 361 | 0x3A1E: ('CAR_TELEPHONE_NUMBER', 'STRING'), 362 | 0x3A1F: ('OTHER_TELEPHONE_NUMBER', 'STRING'), 363 | 0x3A20: ('TRANSMITABLE_DISPLAY_NAME', 'STRING'), 364 | 0x3A21: ('PAGER_TELEPHONE_NUMBER', 'STRING'), 365 | 0x3A22: ('USER_CERTIFICATE', 'BINARY'), 366 | 0x3A23: ('PRIMARY_FAX_NUMBER', 'STRING'), 367 | 0x3A24: ('BUSINESS_FAX_NUMBER', 'STRING'), 368 | 0x3A25: ('HOME_FAX_NUMBER', 'STRING'), 369 | 0x3A26: ('COUNTRY', 'STRING'), 370 | 0x3A27: ('LOCALITY', 'STRING'), 371 | 0x3A28: ('STATE_OR_PROVINCE', 'STRING'), 372 | 0x3A29: ('STREET_ADDRESS', 'STRING'), 373 | 0x3A2A: ('POSTAL_CODE', 'STRING'), 374 | 0x3A2B: ('POST_OFFICE_BOX', 'STRING'), 375 | 0x3A2C: ('TELEX_NUMBER', 'STRING'), 376 | 0x3A2D: ('ISDN_NUMBER', 'STRING'), 377 | 0x3A2E: ('ASSISTANT_TELEPHONE_NUMBER', 'STRING'), 378 | 0x3A2F: ('HOME2_TELEPHONE_NUMBER', 'STRING'), 379 | 0x3A30: ('ASSISTANT', 'STRING'), 380 | 0x3A40: ('SEND_RICH_INFO', 'BOOLEAN'), 381 | 0x3A41: ('WEDDING_ANNIVERSARY', 'SYSTIME'), 382 | 0x3A42: ('BIRTHDAY', 'SYSTIME'), 383 | 0x3A43: ('HOBBIES', 'STRING'), 384 | 0x3A44: ('MIDDLE_NAME', 'STRING'), 385 | 0x3A45: ('DISPLAY_NAME_PREFIX', 'STRING'), 386 | 0x3A46: ('PROFESSION', 'STRING'), 387 | 0x3A47: ('PREFERRED_BY_NAME', 'STRING'), 388 | 0x3A48: ('SPOUSE_NAME', 'STRING'), 389 | 0x3A49: ('COMPUTER_NETWORK_NAME', 'STRING'), 390 | 0x3A4A: ('CUSTOMER_ID', 'STRING'), 391 | 0x3A4B: ('TTYTDD_PHONE_NUMBER', 'STRING'), 392 | 0x3A4C: ('FTP_SITE', 'STRING'), 393 | 0x3A4D: ('GENDER', 'I2'), 394 | 0x3A4E: ('MANAGER_NAME', 'STRING'), 395 | 0x3A4F: ('NICKNAME', 'STRING'), 396 | 0x3A50: ('PERSONAL_HOME_PAGE', 'STRING'), 397 | 0x3A51: ('BUSINESS_HOME_PAGE', 'STRING'), 398 | 0x3A52: ('CONTACT_VERSION', 'CLSID'), 399 | 0x3A53: ('CONTACT_ENTRYIDS', 'MV_BINARY'), 400 | 0x3A54: ('CONTACT_ADDRTYPES', 'MV_STRING'), 401 | 0x3A55: ('CONTACT_DEFAULT_ADDRESS_INDEX', 'I4'), 402 | 0x3A56: ('CONTACT_EMAIL_ADDRESSES', 'MV_STRING'), 403 | 0x3A57: ('COMPANY_MAIN_PHONE_NUMBER', 'STRING'), 404 | 0x3A58: ('CHILDRENS_NAMES', 'MV_STRING'), 405 | 0x3A59: ('HOME_ADDRESS_CITY', 'STRING'), 406 | 0x3A5A: ('HOME_ADDRESS_COUNTRY', 'STRING'), 407 | 0x3A5B: ('HOME_ADDRESS_POSTAL_CODE', 'STRING'), 408 | 0x3A5C: ('HOME_ADDRESS_STATE_OR_PROVINCE', 'STRING'), 409 | 0x3A5D: ('HOME_ADDRESS_STREET', 'STRING'), 410 | 0x3A5E: ('HOME_ADDRESS_POST_OFFICE_BOX', 'STRING'), 411 | 0x3A5F: ('OTHER_ADDRESS_CITY', 'STRING'), 412 | 0x3A60: ('OTHER_ADDRESS_COUNTRY', 'STRING'), 413 | 0x3A61: ('OTHER_ADDRESS_POSTAL_CODE', 'STRING'), 414 | 0x3A62: ('OTHER_ADDRESS_STATE_OR_PROVINCE', 'STRING'), 415 | 0x3A63: ('OTHER_ADDRESS_STREET', 'STRING'), 416 | 0x3A64: ('OTHER_ADDRESS_POST_OFFICE_BOX', 'STRING'), 417 | 0x3D00: ('STORE_PROVIDERS', 'BINARY'), 418 | 0x3D01: ('AB_PROVIDERS', 'BINARY'), 419 | 0x3D02: ('TRANSPORT_PROVIDERS', 'BINARY'), 420 | 0x3D04: ('DEFAULT_PROFILE', 'BOOLEAN'), 421 | 0x3D05: ('AB_SEARCH_PATH', 'MV_BINARY'), 422 | 0x3D06: ('AB_DEFAULT_DIR', 'BINARY'), 423 | 0x3D07: ('AB_DEFAULT_PAB', 'BINARY'), 424 | 0x3D09: ('SERVICE_NAME', 'STRING'), 425 | 0x3D0A: ('SERVICE_DLL_NAME', 'STRING'), 426 | 0x3D0B: ('SERVICE_ENTRY_NAME', 'STRING'), 427 | 0x3D0C: ('SERVICE_UID', 'BINARY'), 428 | 0x3D0D: ('SERVICE_EXTRA_UIDS', 'BINARY'), 429 | 0x3D0E: ('SERVICES', 'BINARY'), 430 | 0x3D0F: ('SERVICE_SUPPORT_FILES', 'MV_STRING'), 431 | 0x3D10: ('SERVICE_DELETE_FILES', 'MV_STRING'), 432 | 0x3D11: ('AB_SEARCH_PATH_UPDATE', 'BINARY'), 433 | 0x3D12: ('PROFILE_NAME', 'STRING'), 434 | 0x3E00: ('IDENTITY_DISPLAY', 'STRING'), 435 | 0x3E01: ('IDENTITY_ENTRYID', 'BINARY'), 436 | 0x3E02: ('RESOURCE_METHODS', 'I4'), 437 | # Service provider type 438 | 0x3E03: ('RESOURCE_TYPE', 'I4'), 439 | 0x3E04: ('STATUS_CODE', 'I4'), 440 | 0x3E05: ('IDENTITY_SEARCH_KEY', 'BINARY'), 441 | 0x3E06: ('OWN_STORE_ENTRYID', 'BINARY'), 442 | 0x3E07: ('RESOURCE_PATH', 'STRING'), 443 | 0x3E08: ('STATUS_STRING', 'STRING'), 444 | 0x3E09: ('X400_DEFERRED_DELIVERY_CANCEL', 'BOOLEAN'), 445 | 0x3E0A: ('HEADER_FOLDER_ENTRYID', 'BINARY'), 446 | 0x3E0B: ('REMOTE_PROGRESS', 'I4'), 447 | 0x3E0C: ('REMOTE_PROGRESS_TEXT', 'STRING'), 448 | 0x3E0D: ('REMOTE_VALIDATE_OK', 'BOOLEAN'), 449 | 0x3F00: ('CONTROL_FLAGS', 'I4'), 450 | 0x3F01: ('CONTROL_STRUCTURE', 'BINARY'), 451 | 0x3F02: ('CONTROL_TYPE', 'I4'), 452 | 0x3F03: ('DELTAX', 'I4'), 453 | 0x3F04: ('DELTAY', 'I4'), 454 | 0x3F05: ('XPOS', 'I4'), 455 | 0x3F06: ('YPOS', 'I4'), 456 | 0x3F07: ('CONTROL_ID', 'BINARY'), 457 | 0x3F08: ('INITIAL_DETAILS_PANE', 'I4'), 458 | } -------------------------------------------------------------------------------- /src/main/java/org/simplejavamail/jakarta/mail/internet/InternetHeaders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program and the accompanying materials are made available under the 5 | * terms of the Eclipse Public License v. 2.0, which is available at 6 | * http://www.eclipse.org/legal/epl-2.0. 7 | * 8 | * This Source Code may also be made available under the following Secondary 9 | * Licenses when the conditions for such availability set forth in the 10 | * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, 11 | * version 2 with the GNU Classpath Exception, which is available at 12 | * https://www.gnu.org/software/classpath/license.html. 13 | * 14 | * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 15 | */ 16 | 17 | package org.simplejavamail.jakarta.mail.internet; 18 | 19 | import org.simplejavamail.com.sun.mail.util.LineInputStream; 20 | import org.simplejavamail.com.sun.mail.util.PropUtil; 21 | import org.simplejavamail.jakarta.mail.*; 22 | 23 | import java.io.*; 24 | import java.util.*; 25 | 26 | /** 27 | * InternetHeaders is a utility class that manages RFC822 style 28 | * headers. Given an RFC822 format message stream, it reads lines 29 | * until the blank line that indicates end of header. The input stream 30 | * is positioned at the start of the body. The lines are stored 31 | * within the object and can be extracted as either Strings or 32 | * {@link org.simplejavamail.jakarta.mail.Header} objects.

33 | * 34 | * This class is mostly intended for service providers. MimeMessage 35 | * and MimeBody use this class for holding their headers. 36 | * 37 | *


A note on RFC822 and MIME headers

38 | * 39 | * RFC822 and MIME header fields must contain only 40 | * US-ASCII characters. If a header contains non US-ASCII characters, 41 | * it must be encoded as per the rules in RFC 2047. The MimeUtility 42 | * class provided in this package can be used to to achieve this. 43 | * Callers of the setHeader, addHeader, and 44 | * addHeaderLine methods are responsible for enforcing 45 | * the MIME requirements for the specified headers. In addition, these 46 | * header fields must be folded (wrapped) before being sent if they 47 | * exceed the line length limitation for the transport (1000 bytes for 48 | * SMTP). Received headers may have been folded. The application is 49 | * responsible for folding and unfolding headers as appropriate.

50 | * 51 | * The current implementation supports the System property 52 | * mail.mime.ignorewhitespacelines, which if set to true 53 | * will cause a line containing only whitespace to be considered 54 | * a blank line terminating the header. 55 | * 56 | * @see org.simplejavamail.jakarta.mail.internet.MimeUtility 57 | * @author John Mani 58 | * @author Bill Shannon 59 | */ 60 | 61 | public class InternetHeaders { 62 | private static final boolean ignoreWhitespaceLines = 63 | PropUtil.getBooleanSystemProperty("mail.mime.ignorewhitespacelines", 64 | false); 65 | 66 | /** 67 | * An individual internet header. This class is only used by 68 | * subclasses of InternetHeaders.

69 | * 70 | * An InternetHeader object with a null value is used as a placeholder 71 | * for headers of that name, to preserve the order of headers. 72 | * A placeholder InternetHeader object with a name of ":" marks 73 | * the location in the list of headers where new headers are 74 | * added by default. 75 | * 76 | * @since JavaMail 1.4 77 | */ 78 | protected static final class InternetHeader extends Header { 79 | /* 80 | * Note that the value field from the superclass 81 | * isn't used in this class. We extract the value 82 | * from the line field as needed. We store the line 83 | * rather than just the value to ensure that we can 84 | * get back the exact original line, with the original 85 | * whitespace, etc. 86 | */ 87 | String line; // the entire RFC822 header "line", 88 | // or null if placeholder 89 | 90 | /** 91 | * Constructor that takes a line and splits out 92 | * the header name. 93 | * 94 | * @param l the header line 95 | */ 96 | public InternetHeader(String l) { 97 | super("", ""); // XXX - we'll change it later 98 | int i = l.indexOf(':'); 99 | if (i < 0) { 100 | // should never happen 101 | name = l.trim(); 102 | } else { 103 | name = l.substring(0, i).trim(); 104 | } 105 | line = l; 106 | } 107 | 108 | /** 109 | * Constructor that takes a header name and value. 110 | * 111 | * @param n the name of the header 112 | * @param v the value of the header 113 | */ 114 | public InternetHeader(String n, String v) { 115 | super(n, ""); 116 | if (v != null) 117 | line = n + ": " + v; 118 | else 119 | line = null; 120 | } 121 | 122 | /** 123 | * Return the "value" part of the header line. 124 | */ 125 | @Override 126 | public String getValue() { 127 | int i = line.indexOf(':'); 128 | if (i < 0) 129 | return line; 130 | // skip whitespace after ':' 131 | int j; 132 | for (j = i + 1; j < line.length(); j++) { 133 | char c = line.charAt(j); 134 | if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) 135 | break; 136 | } 137 | return line.substring(j); 138 | } 139 | } 140 | 141 | /* 142 | * The enumeration object used to enumerate an 143 | * InternetHeaders object. Can return 144 | * either a String or a Header object. 145 | */ 146 | static class MatchEnum { 147 | private Iterator e; // enum object of headers List 148 | // XXX - is this overkill? should we step through in index 149 | // order instead? 150 | private String names[]; // names to match, or not 151 | private boolean match; // return matching headers? 152 | private boolean want_line; // return header lines? 153 | private InternetHeader next_header; // the next header to be returned 154 | 155 | /* 156 | * Constructor. Initialize the enumeration for the entire 157 | * List of headers, the set of headers, whether to return 158 | * matching or non-matching headers, and whether to return 159 | * header lines or Header objects. 160 | */ 161 | MatchEnum(List v, String n[], boolean m, boolean l) { 162 | e = v.iterator(); 163 | names = n; 164 | match = m; 165 | want_line = l; 166 | next_header = null; 167 | } 168 | 169 | /* 170 | * Any more elements in this enumeration? 171 | */ 172 | public boolean hasMoreElements() { 173 | // if necessary, prefetch the next matching header, 174 | // and remember it. 175 | if (next_header == null) 176 | next_header = nextMatch(); 177 | return next_header != null; 178 | } 179 | 180 | /* 181 | * Return the next element. 182 | */ 183 | public Object nextElement() { 184 | if (next_header == null) 185 | next_header = nextMatch(); 186 | 187 | if (next_header == null) 188 | throw new NoSuchElementException("No more headers"); 189 | 190 | InternetHeader h = next_header; 191 | next_header = null; 192 | if (want_line) 193 | return h.line; 194 | else 195 | return new Header(h.getName(), h.getValue()); 196 | } 197 | 198 | /* 199 | * Return the next Header object according to the match 200 | * criteria, or null if none left. 201 | */ 202 | private InternetHeader nextMatch() { 203 | next: 204 | while (e.hasNext()) { 205 | InternetHeader h = e.next(); 206 | 207 | // skip "place holder" headers 208 | if (h.line == null) 209 | continue; 210 | 211 | // if no names to match against, return appropriately 212 | if (names == null) 213 | return match ? null : h; 214 | 215 | // check whether this header matches any of the names 216 | for (int i = 0; i < names.length; i++) { 217 | if (names[i].equalsIgnoreCase(h.getName())) { 218 | if (match) 219 | return h; 220 | else 221 | // found a match, but we're 222 | // looking for non-matches. 223 | // try next header. 224 | continue next; 225 | } 226 | } 227 | // found no matches. if that's what we wanted, return it. 228 | if (!match) 229 | return h; 230 | } 231 | return null; 232 | } 233 | } 234 | 235 | static class MatchStringEnum extends MatchEnum 236 | implements Enumeration { 237 | 238 | MatchStringEnum(List v, String[] n, boolean m) { 239 | super(v, n, m, true); 240 | } 241 | 242 | @Override 243 | public String nextElement() { 244 | return (String) super.nextElement(); 245 | } 246 | 247 | } 248 | 249 | static class MatchHeaderEnum extends MatchEnum 250 | implements Enumeration

{ 251 | 252 | MatchHeaderEnum(List v, String[] n, boolean m) { 253 | super(v, n, m, false); 254 | } 255 | 256 | @Override 257 | public Header nextElement() { 258 | return (Header) super.nextElement(); 259 | } 260 | 261 | } 262 | 263 | /** 264 | * The actual list of Headers, including placeholder entries. 265 | * Placeholder entries are Headers with a null value and 266 | * are never seen by clients of the InternetHeaders class. 267 | * Placeholder entries are used to keep track of the preferred 268 | * order of headers. Headers are never actually removed from 269 | * the list, they're converted into placeholder entries. 270 | * New headers are added after existing headers of the same name 271 | * (or before in the case of Received and 272 | * Return-Path headers). If no existing header 273 | * or placeholder for the header is found, new headers are 274 | * added after the special placeholder with the name ":". 275 | * 276 | * @since JavaMail 1.4 277 | */ 278 | protected List headers; 279 | 280 | /** 281 | * Create an empty InternetHeaders object. Placeholder entries 282 | * are inserted to indicate the preferred order of headers. 283 | */ 284 | public InternetHeaders() { 285 | headers = new ArrayList<>(40); 286 | headers.add(new InternetHeader("Return-Path", null)); 287 | headers.add(new InternetHeader("Received", null)); 288 | headers.add(new InternetHeader("Resent-Date", null)); 289 | headers.add(new InternetHeader("Resent-From", null)); 290 | headers.add(new InternetHeader("Resent-Sender", null)); 291 | headers.add(new InternetHeader("Resent-To", null)); 292 | headers.add(new InternetHeader("Resent-Cc", null)); 293 | headers.add(new InternetHeader("Resent-Bcc", null)); 294 | headers.add(new InternetHeader("Resent-Message-Id", null)); 295 | headers.add(new InternetHeader("Date", null)); 296 | headers.add(new InternetHeader("From", null)); 297 | headers.add(new InternetHeader("Sender", null)); 298 | headers.add(new InternetHeader("Reply-To", null)); 299 | headers.add(new InternetHeader("To", null)); 300 | headers.add(new InternetHeader("Cc", null)); 301 | headers.add(new InternetHeader("Bcc", null)); 302 | headers.add(new InternetHeader("Message-Id", null)); 303 | headers.add(new InternetHeader("In-Reply-To", null)); 304 | headers.add(new InternetHeader("References", null)); 305 | headers.add(new InternetHeader("Subject", null)); 306 | headers.add(new InternetHeader("Comments", null)); 307 | headers.add(new InternetHeader("Keywords", null)); 308 | headers.add(new InternetHeader("Errors-To", null)); 309 | headers.add(new InternetHeader("MIME-Version", null)); 310 | headers.add(new InternetHeader("Content-Type", null)); 311 | headers.add(new InternetHeader("Content-Transfer-Encoding", null)); 312 | headers.add(new InternetHeader("Content-MD5", null)); 313 | headers.add(new InternetHeader(":", null)); 314 | headers.add(new InternetHeader("Content-Length", null)); 315 | headers.add(new InternetHeader("Status", null)); 316 | } 317 | 318 | /** 319 | * Read and parse the given RFC822 message stream till the 320 | * blank line separating the header from the body. The input 321 | * stream is left positioned at the start of the body. The 322 | * header lines are stored internally.

323 | * 324 | * For efficiency, wrap a BufferedInputStream around the actual 325 | * input stream and pass it as the parameter.

326 | * 327 | * No placeholder entries are inserted; the original order of 328 | * the headers is preserved. 329 | * 330 | * @param is RFC822 input stream 331 | * @exception MessagingException for any I/O error reading the stream 332 | */ 333 | public InternetHeaders(InputStream is) throws MessagingException { 334 | this(is, false); 335 | } 336 | 337 | /** 338 | * Read and parse the given RFC822 message stream till the 339 | * blank line separating the header from the body. The input 340 | * stream is left positioned at the start of the body. The 341 | * header lines are stored internally.

342 | * 343 | * For efficiency, wrap a BufferedInputStream around the actual 344 | * input stream and pass it as the parameter.

345 | * 346 | * No placeholder entries are inserted; the original order of 347 | * the headers is preserved. 348 | * 349 | * @param is RFC822 input stream 350 | * @param allowutf8 if UTF-8 encoded headers are allowed 351 | * @exception MessagingException for any I/O error reading the stream 352 | * @since JavaMail 1.6 353 | */ 354 | public InternetHeaders(InputStream is, boolean allowutf8) 355 | throws MessagingException { 356 | headers = new ArrayList<>(40); 357 | load(is, allowutf8); 358 | } 359 | 360 | /** 361 | * Read and parse the given RFC822 message stream till the 362 | * blank line separating the header from the body. Store the 363 | * header lines inside this InternetHeaders object. The order 364 | * of header lines is preserved.

365 | * 366 | * Note that the header lines are added into this InternetHeaders 367 | * object, so any existing headers in this object will not be 368 | * affected. Headers are added to the end of the existing list 369 | * of headers, in order. 370 | * 371 | * @param is RFC822 input stream 372 | * @exception MessagingException for any I/O error reading the stream 373 | */ 374 | public void load(InputStream is) throws MessagingException { 375 | load(is, false); 376 | } 377 | 378 | /** 379 | * Read and parse the given RFC822 message stream till the 380 | * blank line separating the header from the body. Store the 381 | * header lines inside this InternetHeaders object. The order 382 | * of header lines is preserved.

383 | * 384 | * Note that the header lines are added into this InternetHeaders 385 | * object, so any existing headers in this object will not be 386 | * affected. Headers are added to the end of the existing list 387 | * of headers, in order. 388 | * 389 | * @param is RFC822 input stream 390 | * @param allowutf8 if UTF-8 encoded headers are allowed 391 | * @exception MessagingException for any I/O error reading the stream 392 | * @since JavaMail 1.6 393 | */ 394 | public void load(InputStream is, boolean allowutf8) 395 | throws MessagingException { 396 | // Read header lines until a blank line. It is valid 397 | // to have BodyParts with no header lines. 398 | String line; 399 | LineInputStream lis = new LineInputStream(is, allowutf8); 400 | String prevline = null; // the previous header line, as a string 401 | // a buffer to accumulate the header in, when we know it's needed 402 | StringBuilder lineBuffer = new StringBuilder(); 403 | 404 | try { 405 | // if the first line being read is a continuation line, 406 | // we ignore it if it's otherwise empty or we treat it as 407 | // a non-continuation line if it has non-whitespace content 408 | boolean first = true; 409 | do { 410 | line = lis.readLine(); 411 | if (line != null && 412 | (line.startsWith(" ") || line.startsWith("\t"))) { 413 | // continuation of header 414 | if (prevline != null) { 415 | lineBuffer.append(prevline); 416 | prevline = null; 417 | } 418 | if (first) { 419 | String lt = line.trim(); 420 | if (lt.length() > 0) 421 | lineBuffer.append(lt); 422 | } else { 423 | if (lineBuffer.length() > 0) 424 | lineBuffer.append("\r\n"); 425 | lineBuffer.append(line); 426 | } 427 | } else { 428 | // new header 429 | if (prevline != null) 430 | addHeaderLine(prevline); 431 | else if (lineBuffer.length() > 0) { 432 | // store previous header first 433 | addHeaderLine(lineBuffer.toString()); 434 | lineBuffer.setLength(0); 435 | } 436 | prevline = line; 437 | } 438 | first = false; 439 | } while (line != null && !isEmpty(line)); 440 | } catch (IOException ioex) { 441 | throw new MessagingException("Error in input stream", ioex); 442 | } 443 | } 444 | 445 | /** 446 | * Is this line an empty (blank) line? 447 | */ 448 | private static final boolean isEmpty(String line) { 449 | return line.length() == 0 || 450 | (ignoreWhitespaceLines && line.trim().length() == 0); 451 | } 452 | 453 | /** 454 | * Return all the values for the specified header. The 455 | * values are String objects. Returns null 456 | * if no headers with the specified name exist. 457 | * 458 | * @param name header name 459 | * @return array of header values, or null if none 460 | */ 461 | public String[] getHeader(String name) { 462 | Iterator e = headers.iterator(); 463 | // XXX - should we just step through in index order? 464 | List v = new ArrayList<>(); // accumulate return values 465 | 466 | while (e.hasNext()) { 467 | InternetHeader h = e.next(); 468 | if (name.equalsIgnoreCase(h.getName()) && h.line != null) { 469 | v.add(h.getValue()); 470 | } 471 | } 472 | if (v.size() == 0) 473 | return (null); 474 | // convert List to an array for return 475 | String r[] = new String[v.size()]; 476 | r = v.toArray(r); 477 | return (r); 478 | } 479 | 480 | /** 481 | * Get all the headers for this header name, returned as a single 482 | * String, with headers separated by the delimiter. If the 483 | * delimiter is null, only the first header is 484 | * returned. Returns null 485 | * if no headers with the specified name exist. 486 | * 487 | * @param name header name 488 | * @param delimiter delimiter 489 | * @return the value fields for all headers with 490 | * this name, or null if none 491 | */ 492 | public String getHeader(String name, String delimiter) { 493 | String s[] = getHeader(name); 494 | 495 | if (s == null) 496 | return null; 497 | 498 | if ((s.length == 1) || delimiter == null) 499 | return s[0]; 500 | 501 | StringBuilder r = new StringBuilder(s[0]); 502 | for (int i = 1; i < s.length; i++) { 503 | r.append(delimiter); 504 | r.append(s[i]); 505 | } 506 | return r.toString(); 507 | } 508 | 509 | /** 510 | * Change the first header line that matches name 511 | * to have value, adding a new header if no existing header 512 | * matches. Remove all matching headers but the first.

513 | * 514 | * Note that RFC822 headers can only contain US-ASCII characters 515 | * 516 | * @param name header name 517 | * @param value header value 518 | */ 519 | public void setHeader(String name, String value) { 520 | boolean found = false; 521 | 522 | for (int i = 0; i < headers.size(); i++) { 523 | InternetHeader h = headers.get(i); 524 | if (name.equalsIgnoreCase(h.getName())) { 525 | if (!found) { 526 | int j; 527 | if (h.line != null && (j = h.line.indexOf(':')) >= 0) { 528 | h.line = h.line.substring(0, j + 1) + " " + value; 529 | // preserves capitalization, spacing 530 | } else { 531 | h.line = name + ": " + value; 532 | } 533 | found = true; 534 | } else { 535 | headers.remove(i); 536 | i--; // have to look at i again 537 | } 538 | } 539 | } 540 | 541 | if (!found) { 542 | addHeader(name, value); 543 | } 544 | } 545 | 546 | /** 547 | * Add a header with the specified name and value to the header list.

548 | * 549 | * The current implementation knows about the preferred order of most 550 | * well-known headers and will insert headers in that order. In 551 | * addition, it knows that Received headers should be 552 | * inserted in reverse order (newest before oldest), and that they 553 | * should appear at the beginning of the headers, preceeded only by 554 | * a possible Return-Path header.

555 | * 556 | * Note that RFC822 headers can only contain US-ASCII characters. 557 | * 558 | * @param name header name 559 | * @param value header value 560 | */ 561 | public void addHeader(String name, String value) { 562 | int pos = headers.size(); 563 | boolean addReverse = 564 | name.equalsIgnoreCase("Received") || 565 | name.equalsIgnoreCase("Return-Path"); 566 | if (addReverse) 567 | pos = 0; 568 | for (int i = headers.size() - 1; i >= 0; i--) { 569 | InternetHeader h = headers.get(i); 570 | if (name.equalsIgnoreCase(h.getName())) { 571 | if (addReverse) { 572 | pos = i; 573 | } else { 574 | headers.add(i + 1, new InternetHeader(name, value)); 575 | return; 576 | } 577 | } 578 | // marker for default place to add new headers 579 | if (!addReverse && h.getName().equals(":")) 580 | pos = i; 581 | } 582 | headers.add(pos, new InternetHeader(name, value)); 583 | } 584 | 585 | /** 586 | * Remove all header entries that match the given name 587 | * @param name header name 588 | */ 589 | public void removeHeader(String name) { 590 | for (int i = 0; i < headers.size(); i++) { 591 | InternetHeader h = headers.get(i); 592 | if (name.equalsIgnoreCase(h.getName())) { 593 | h.line = null; 594 | //headers.remove(i); 595 | //i--; // have to look at i again 596 | } 597 | } 598 | } 599 | 600 | /** 601 | * Return all the headers as an Enumeration of 602 | * {@link org.simplejavamail.jakarta.mail.Header} objects. 603 | * 604 | * @return Enumeration of Header objects 605 | */ 606 | public Enumeration

getAllHeaders() { 607 | return (new MatchHeaderEnum(headers, null, false)); 608 | } 609 | 610 | /** 611 | * Return all matching {@link org.simplejavamail.jakarta.mail.Header} objects. 612 | * 613 | * @param names the headers to return 614 | * @return Enumeration of matching Header objects 615 | */ 616 | public Enumeration
getMatchingHeaders(String[] names) { 617 | return (new MatchHeaderEnum(headers, names, true)); 618 | } 619 | 620 | /** 621 | * Return all non-matching {@link org.simplejavamail.jakarta.mail.Header} objects. 622 | * 623 | * @param names the headers to not return 624 | * @return Enumeration of non-matching Header objects 625 | */ 626 | public Enumeration
getNonMatchingHeaders(String[] names) { 627 | return (new MatchHeaderEnum(headers, names, false)); 628 | } 629 | 630 | /** 631 | * Add an RFC822 header line to the header store. 632 | * If the line starts with a space or tab (a continuation line), 633 | * add it to the last header line in the list. Otherwise, 634 | * append the new header line to the list.

635 | * 636 | * Note that RFC822 headers can only contain US-ASCII characters 637 | * 638 | * @param line raw RFC822 header line 639 | */ 640 | public void addHeaderLine(String line) { 641 | try { 642 | char c = line.charAt(0); 643 | if (c == ' ' || c == '\t') { 644 | InternetHeader h = headers.get(headers.size() - 1); 645 | h.line += "\r\n" + line; 646 | } else 647 | headers.add(new InternetHeader(line)); 648 | } catch (StringIndexOutOfBoundsException e) { 649 | // line is empty, ignore it 650 | return; 651 | } catch (NoSuchElementException e) { 652 | // XXX - list is empty? 653 | } 654 | } 655 | 656 | /** 657 | * Return all the header lines as an Enumeration of Strings. 658 | * 659 | * @return Enumeration of Strings of all header lines 660 | */ 661 | public Enumeration getAllHeaderLines() { 662 | return (getNonMatchingHeaderLines(null)); 663 | } 664 | 665 | /** 666 | * Return all matching header lines as an Enumeration of Strings. 667 | * 668 | * @param names the headers to return 669 | * @return Enumeration of Strings of all matching header lines 670 | */ 671 | public Enumeration getMatchingHeaderLines(String[] names) { 672 | return (new MatchStringEnum(headers, names, true)); 673 | } 674 | 675 | /** 676 | * Return all non-matching header lines 677 | * 678 | * @param names the headers to not return 679 | * @return Enumeration of Strings of all non-matching header lines 680 | */ 681 | public Enumeration getNonMatchingHeaderLines(String[] names) { 682 | return (new MatchStringEnum(headers, names, false)); 683 | } 684 | } 685 | --------------------------------------------------------------------------------