//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/message/AS2MessageParser.java,v 1.1 2012/04/18 14:10:30 heller Exp $
package de.mendelson.comm.as2.message;
import de.mendelson.comm.as2.AS2Exception;
import de.mendelson.comm.as2.notification.Notification;
import de.mendelson.util.security.cert.CertificateManager;
import de.mendelson.comm.as2.partner.Partner;
import de.mendelson.comm.as2.partner.PartnerAccessDB;
import de.mendelson.util.AS2Tools;
import de.mendelson.util.MecResourceBundle;
import de.mendelson.util.security.BCCryptoHelper;
import javax.mail.util.ByteArrayDataSource;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.activation.DataHandler;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import org.apache.commons.io.output.DeferredFileOutputStream;
import org.bouncycastle.cms.RecipientId;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
import org.bouncycastle.mail.smime.SMIMECompressed;
import org.bouncycastle.mail.smime.SMIMEEnveloped;
/*
* Copyright (C) mendelson-e-commerce GmbH Berlin Germany
*
* This software is subject to the license agreement set forth in the license.
* Please read and agree to all terms before using this software.
* Other product and brand names are trademarks of their respective owners.
*/
/**
* Analyzes and builds AS2 messages
* @author S.Heller
* @version $Revision: 1.1 $
*/
public class AS2MessageParser {
private Logger logger = null;
/**Access to all certificates*/
private CertificateManager certificateManagerSignature;
private CertificateManager certificateManagerEncryption;
private MecResourceBundle rb = null;
private MecResourceBundle rbMessage = null;
//DB connection
private Connection configConnection = null;
private Connection runtimeConnection = null;
public AS2MessageParser() {
//Load resourcebundle
try {
this.rb = (MecResourceBundle) ResourceBundle.getBundle(
ResourceBundleAS2MessageParser.class.getName());
this.rbMessage = (MecResourceBundle) ResourceBundle.getBundle(
ResourceBundleAS2Message.class.getName());
} //load up resourcebundle
catch (MissingResourceException e) {
throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found.");
}
}
/**Passes the certificate manager to this class*/
public void setCertificateManager(CertificateManager certificateManagerSignature, CertificateManager certificateManagerEncryption) {
this.certificateManagerEncryption = certificateManagerEncryption;
this.certificateManagerSignature = certificateManagerSignature;
}
/**Passes a db connection to this class*/
public void setDBConnection(Connection configConnection, Connection runtimeConnection) {
this.configConnection = configConnection;
this.runtimeConnection = runtimeConnection;
}
/**Passes a logger to this class*/
public void setLogger(Logger logger) {
this.logger = logger;
}
/**Unescapes the AS2-TO and AS2-FROM headers in receiver direction, related to
* RFC 4130 section 6.2
* @param identification as2-from or as2-to value to unescape
* @return unescaped value
*/
public static String unescapeFromToHeader(String identification) {
StringBuilder builder = new StringBuilder();
//remove quotes
if (identification.startsWith("\"") && identification.endsWith("\"")) {
identification = identification.substring(1, identification.length() - 1);
}
boolean inEscape = false;
for (int i = 0; i < identification.length(); i++) {
char singleChar = identification.charAt(i);
if (singleChar == '\\') {
if (inEscape) {
builder.append(singleChar);
inEscape = false;
} else {
inEscape = true;
}
} else {
builder.append(singleChar);
inEscape = false;
}
}
return (builder.toString());
}
/**Displays a bundle of byte arrays as hex string, for debug purpose only*/
private String toHexDisplay(byte[] data) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < data.length; i++) {
result.append(Integer.toString((data[i] & 0xff) + 0x100, 16).substring(1));
result.append(" ");
}
return result.toString();
}
/**Uncompresses message data*/
public byte[] decompressData(AS2MessageInfo info, byte[] data, String contentType) throws Exception {
MimeBodyPart compressedPart = new MimeBodyPart();
compressedPart.setDataHandler(new DataHandler(new ByteArrayDataSource(data, contentType)));
compressedPart.setHeader("content-type", contentType);
return (this.decompressData(info, new SMIMECompressed(compressedPart), compressedPart.getSize()));
}
/**Uncompresses message data*/
public byte[] decompressData(AS2MessageInfo info, SMIMECompressed compressed, long compressedSize) throws Exception {
byte[] decompressedData = compressed.getContent();
info.setCompressionType(AS2Message.COMPRESSION_ZLIB);
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("data.compressed.expanded",
new Object[]{
info.getMessageId(), AS2Tools.getDataSizeDisplay(compressedSize),
AS2Tools.getDataSizeDisplay(decompressedData.length)
}), info);
}
return (decompressedData);
}
/**Decodes data by its content transfer encoding and returns it*/
private byte[] decodeContentTransferEncoding(byte[] encodedData, String contentTransferEncoding) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(encodedData);
InputStream b64is = MimeUtility.decode(bais, contentTransferEncoding);
byte[] tmp = new byte[encodedData.length];
int n = b64is.read(tmp);
byte[] res = new byte[n];
System.arraycopy(tmp, 0, res, 0, n);
return res;
}
/**If a content transfer encoding is set this will decode the data*/
private byte[] processContentTransferEncoding(byte[] data, Properties header) throws Exception {
if (!header.containsKey("content-transfer-encoding")) {
//no content transfer encoding set: the AS2 default is "binary" in this case (NOT 7bit!), binary
//content transfer encoding requires no decoding
return (data);
} else {
String transferEncoding = header.getProperty("content-transfer-encoding");
return (this.decodeContentTransferEncoding(data, transferEncoding));
}
}
private AS2Message createFromMDNRequest(byte[] rawMessageData, Properties header, String contentType, AS2MDNInfo mdnInfo, MDNParser mdnParser) throws Exception {
AS2MessageInfo relatedMessageInfo = new AS2MessageInfo();
relatedMessageInfo.setMessageId(mdnInfo.getRelatedMessageId());
//generate a new MDN id for this MDN if none is provided. message ids are not required for MDN in the RFC:
//section 7.6 (Receipt Reply Considerations in HTTP POST) --> "an MDN SHOULD have its own unique Message-ID header."
if (mdnInfo.getMessageId() == null) {
mdnInfo.setMessageId("<MDN_ON_" + mdnInfo.getRelatedMessageId() + ">");
}
MDNAccessDB mdnAccess = new MDNAccessDB(this.configConnection, this.runtimeConnection);
MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
//check if related message id exists in db
if (!messageAccess.messageIdExists(mdnInfo.getRelatedMessageId())) {
//there is no way to log this persistent because the MDN is not related to any message: Just write the
//incoming event to the log without binding it to a as message
if (this.logger != null) {
this.logger.log(Level.FINE, this.rb.getResourceString("mdn.incoming", mdnInfo.getMessageId()));
}
throw new Exception(this.rb.getResourceString("mdn.unexpected.messageid",
new Object[]{
mdnInfo.getMessageId(), mdnInfo.getRelatedMessageId()
}));
}
//do not add an MDN to a message that is already ok or stopped
int messageState = messageAccess.getMessageState(mdnInfo.getRelatedMessageId());
if (messageState != AS2Message.STATE_PENDING) {
throw new Exception(this.rb.getResourceString("mdn.unexpected.state",
new Object[]{
mdnInfo.getMessageId(), mdnInfo.getRelatedMessageId()
}));
}
mdnAccess.initializeOrUpdateMDN(mdnInfo);
if (this.logger != null) {
this.logger.log(Level.FINE, this.rb.getResourceString("mdn.incoming", mdnInfo.getMessageId()), relatedMessageInfo);
}
relatedMessageInfo = messageAccess.getLastMessageEntry(mdnInfo.getRelatedMessageId());
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("mdn.answerto",
new Object[]{
mdnInfo.getMessageId(), mdnInfo.getRelatedMessageId()
}), relatedMessageInfo);
}
if (mdnParser.getDispositionState() != null) {
//failure in processing message on remote AS2 server: log the failure and set transaction state
if (mdnParser.getDispositionState().toLowerCase().indexOf("failed") >= 0 || mdnParser.getDispositionState().toLowerCase().indexOf("error") >= 0) {
if (this.logger != null) {
this.logger.log(Level.SEVERE,
this.rb.getResourceString("mdn.state",
new Object[]{
mdnInfo.getMessageId(), mdnParser.getDispositionState()
}), relatedMessageInfo);
this.logger.log(Level.SEVERE,
this.rb.getResourceString("mdn.details",
new Object[]{
mdnInfo.getMessageId(), mdnParser.getMdnDetails()
}), relatedMessageInfo);
}
mdnInfo.setState(AS2Message.STATE_STOPPED);
mdnInfo.setRemoteMDNText("[" + mdnParser.getDispositionState() + "] " + mdnParser.getMdnDetails());
mdnAccess.initializeOrUpdateMDN(mdnInfo);
relatedMessageInfo.setState(AS2Message.STATE_STOPPED);
//update the related message in the database. This has to be performed because
//of the notification
messageAccess.setMessageState(relatedMessageInfo.getMessageId(), AS2Message.STATE_STOPPED);
messageAccess.initializeOrUpdateMessage(relatedMessageInfo);
} else {
//message has been processed: log the found state
if (this.logger != null) {
this.logger.log(Level.FINE,
this.rb.getResourceString("mdn.state",
new Object[]{
mdnInfo.getMessageId(), mdnParser.getDispositionState()
}), relatedMessageInfo);
this.logger.log(Level.FINE,
this.rb.getResourceString("mdn.details",
new Object[]{
mdnInfo.getMessageId(), mdnParser.getMdnDetails()
}), relatedMessageInfo);
}
mdnInfo.setState(AS2Message.STATE_FINISHED);
mdnInfo.setRemoteMDNText("[" + mdnParser.getDispositionState() + "] " + mdnParser.getMdnDetails());
mdnAccess.initializeOrUpdateMDN(mdnInfo);
//relatedMessageInfo.setState(AS2Message.STATE_FINISHED);
messageAccess.initializeOrUpdateMessage(relatedMessageInfo);
}
}
AS2Message message = new AS2Message(mdnInfo);
message.setRawData(rawMessageData);
//check for existing partners
PartnerAccessDB partnerAccess = new PartnerAccessDB(this.configConnection, this.runtimeConnection);
Partner sender = partnerAccess.getPartner(mdnInfo.getSenderId());
Partner receiver = partnerAccess.getPartner(mdnInfo.getReceiverId());
if (sender == null) {
throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
"Sender AS2 id " + mdnInfo.getSenderId() + " is unknown.", message);
}
if (receiver == null) {
throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
"Receiver AS2 id " + mdnInfo.getReceiverId() + " is unknown.", message);
}
if (!receiver.isLocalStation()) {
throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
"The receiver of the message (" + receiver.getAS2Identification() + ") is not defined as a local station.",
message);
}
message.setDecryptedRawData(rawMessageData);
Part payloadPart = this.verifySignature(message, sender, contentType);
//is it a MDN and everything performed well up to here? Then perform the MIC check
if (mdnInfo.getState() == AS2Message.STATE_FINISHED) {
this.checkMDNReceivedContentMIC(mdnInfo);
}
//this is a signed MDN
if (message.getAS2Info().getSignType() != AS2Message.SIGNATURE_NONE) {
this.writePayloadsToMessage(payloadPart, message, header);
} else {
//this is an unsigned mdn
this.writePayloadsToMessage(message.getDecryptedRawData(), message, header);
}
return (message);
}
/**Analyzes and creates passed message data
*/
public AS2Message createMessageFromRequest(byte[] rawMessageData, Properties header, String contentType) throws AS2Exception {
AS2Message message = new AS2Message(new AS2MessageInfo());
if (this.configConnection == null) {
throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
"AS2MessageParser: Pass a DB connection before calling createMessageFromRequest()", message);
}
try {
//decode the content transfer encoding if set
rawMessageData = this.processContentTransferEncoding(rawMessageData, header);
//check if this is a MDN
MDNParser mdnParser = new MDNParser();
AS2MDNInfo mdnInfo = mdnParser.parseMDNData(rawMessageData, contentType);
//its a MDN
if (mdnInfo != null) {
message.setAS2Info(mdnInfo);
mdnInfo.initializeByRequestHeader(header);
return (this.createFromMDNRequest(rawMessageData, header, contentType, mdnInfo, mdnParser));
} else {
AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
messageInfo.initializeByRequestHeader(header);
//inbound AS2 message, no MDN
//no futher processing if the message does not contain a message id
if (messageInfo.getMessageId() == null) {
return (message);
}
//figure out if the MDN should be signed NOW. If an error occurs beyond this point
//the info has to know if the returned MDN should be signed
messageInfo.getDispositionNotificationOptions().setHeaderValue(
header.getProperty("disposition-notification-options"));
//check for existing partners
PartnerAccessDB partnerAccess = new PartnerAccessDB(this.configConnection, this.runtimeConnection);
Partner sender = partnerAccess.getPartner(messageInfo.getSenderId());
Partner receiver = partnerAccess.getPartner(messageInfo.getReceiverId());
if (sender == null) {
throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
"Sender AS2 id " + messageInfo.getSenderId() + " is unknown.", message);
}
if (receiver == null) {
throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
"Receiver AS2 id " + messageInfo.getReceiverId() + " is unknown.", message);
}
if (!receiver.isLocalStation()) {
throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
"The receiver of the message (" + receiver.getAS2Identification() + ") is not defined as a local station.",
message);
}
MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
//check if the message already exists
AS2MessageInfo alreadyExistingInfo = this.messageAlreadyExists(messageAccess, messageInfo.getMessageId());
if( alreadyExistingInfo != null ){
//perform notification: Resend detected, manual interaction might be required
Notification notification = new Notification(this.configConnection, this.runtimeConnection);
notification.sendResendDetected(messageInfo, alreadyExistingInfo, sender, receiver);
throw new AS2Exception(
AS2Exception.PROCESSING_ERROR,
"An AS2 message with the message id " + messageInfo.getMessageId()
+ " has been already processed successfully by the system or is pending ("
+ alreadyExistingInfo.getInitDate() + "). Please "
+ " resubmit the message with a new message id instead or resending it if it should be processed again.",
new AS2Message(messageInfo));
}
messageAccess.initializeOrUpdateMessage(messageInfo);
if (this.logger != null) {
//do not log before because the logging process is related to an already created message in the transaction log
this.logger.log(Level.FINE, this.rb.getResourceString("msg.incoming",
new Object[]{
messageInfo.getMessageId(),
AS2Tools.getDataSizeDisplay(rawMessageData.length)
}), messageInfo);
}
//indicates if a sync or async mdn is requested
messageInfo.setAsyncMDNURL(header.getProperty("receipt-delivery-option"));
messageInfo.setRequestsSyncMDN(header.getProperty("receipt-delivery-option") == null || header.getProperty("receipt-delivery-option").trim().length() == 0);
message.setRawData(rawMessageData);
byte[] decryptedData = this.decryptMessage(message, rawMessageData, contentType, sender, receiver);
//may be already compressed here. Decompress first before going further
if (this.contentTypeIndicatesCompression(contentType)) {
byte[] decompressed = this.decompressData((AS2MessageInfo) message.getAS2Info(), decryptedData, contentType);
message.setDecryptedRawData(decompressed);
//content type has changed now, get it from the decompressed data
ByteArrayInputStream memIn = new ByteArrayInputStream(decompressed);
MimeBodyPart tempPart = new MimeBodyPart(memIn);
memIn.close();
contentType = tempPart.getContentType();
} else {
//check the MIME structure that is embedded in decryptedData for its content type
ByteArrayInputStream memIn = new ByteArrayInputStream(decryptedData);
MimeMessage possibleCompressedPart = new MimeMessage(Session.getInstance(System.getProperties()), memIn);
memIn.close();
if (this.contentTypeIndicatesCompression(possibleCompressedPart.getContentType())) {
long compressedSize = possibleCompressedPart.getSize();
byte[] decompressed = this.decompressData((AS2MessageInfo) message.getAS2Info(), new SMIMECompressed(possibleCompressedPart), compressedSize);
message.setDecryptedRawData(decompressed);
} else {
message.setDecryptedRawData(decryptedData);
}
}
decryptedData = null;
Part payloadPart = this.verifySignature(message, sender, contentType);
//decompress the data if it has been sent compressed, only possible for signed data
if (message.getAS2Info().getSignType() != AS2Message.SIGNATURE_NONE) {
//signed message:
//http://tools.ietf.org/html/draft-ietf-ediint-compression-12
//4.1 MIC Calculation For Signed Message
//For any signed message, the MIC to be returned is calculated over
//the same data that was signed in the original message as per [AS1].
//The signed content will be a mime bodypart that contains either
//compressed or uncompressed data.
this.computeReceivedContentMIC(rawMessageData, message, payloadPart, contentType);
payloadPart = this.decompressData(payloadPart, message);
this.writePayloadsToMessage(payloadPart, message, header);
} else {
//this is an unsigned message
this.writePayloadsToMessage(message.getDecryptedRawData(), message, header);
//unsigned message:
//http://tools.ietf.org/html/draft-ietf-ediint-compression-12
//4.2 MIC Calculation For Encrypted, Unsigned Message
//For encrypted, unsigned messages, the MIC to be returned is
//calculated over the uncompressed data content including all
//MIME header fields and any applied Content-Transfer-Encoding.
//http://tools.ietf.org/html/draft-ietf-ediint-compression-12
//4.3 MIC Calculation For Unencrypted, Unsigned Message
//For unsigned, unencrypted messages, the MIC is calculated
//over the uncompressed data content including all MIME header
//fields and any applied Content-Transfer-Encoding.
this.computeReceivedContentMIC(rawMessageData, message, payloadPart, contentType);
}
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("found.attachments",
new Object[]{messageInfo.getMessageId(), String.valueOf(message.getPayloadCount())}),
messageInfo);
}
return (message);
}
} catch (Exception e) {
if (e instanceof AS2Exception) {
throw (AS2Exception) e;
} else {
throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
e.getMessage(), message);
}
}
}
/**
* Sending implementations MUST be capable of configuring a maximum number
of retries, and MUST stop retrying either when a successful send
occurs or when the total retry number is reached.
* @return
*/
private AS2MessageInfo messageAlreadyExists(MessageAccessDB messageAccess, String messageId) {
List<AS2MessageInfo> infos = messageAccess.getMessageOverview(messageId);
if( infos != null && !infos.isEmpty()){
return( infos.get(0));
}
return( null);
}
/**Checks if the received content mic matches the send message mic. The content mic field is optional if
* the MDN is unsigned, see RFC 4130 section 7.4.3:
* The "Received-content-MIC" extension field is set when the integrity of the received
* message is verified. The MIC is the base64-encoded message-digest computed over the
* received message with a hash function. This field is required for signed receipts but
* optional for unsigned receipts.
*/
private void checkMDNReceivedContentMIC(AS2MDNInfo mdnMessageInfo) throws AS2Exception {
//ignore this check if the received content mic has not been set in the MDN and the MDN is unsigned
if (mdnMessageInfo.getSignType() == AS2Message.SIGNATURE_NONE && mdnMessageInfo.getReceivedContentMIC() == null) {
return;
}
MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
AS2MessageInfo relatedMessageInfo = messageAccess.getLastMessageEntry(mdnMessageInfo.getRelatedMessageId());
BCCryptoHelper helper = new BCCryptoHelper();
if (helper.micIsEqual(mdnMessageInfo.getReceivedContentMIC(), relatedMessageInfo.getReceivedContentMIC())) {
if (this.logger != null) {
this.logger.log(Level.INFO,
this.rb.getResourceString("contentmic.match",
new Object[]{
mdnMessageInfo.getMessageId()}), relatedMessageInfo);
}
} else {
if (this.logger != null) {
this.logger.log(Level.INFO,
this.rb.getResourceString("contentmic.failure",
new Object[]{
mdnMessageInfo.getMessageId(),
relatedMessageInfo.getReceivedContentMIC(),
mdnMessageInfo.getReceivedContentMIC()
}), relatedMessageInfo);
}
//uncomment for ERROR if mic does not match
// throw new AS2Exception(AS2Exception.INTEGRITY_ERROR,
// this.rb.getResourceString("contentmic.failure",
// new Object[]{
// mdnMessageInfo.getMessageId(),
// relatedMessageInfo.getReceivedContentMIC(),
// mdnMessageInfo.getReceivedContentMIC()
// }), message);
}
}
/**Writes a passed payload data to the passed message object. Could be called from either the MDN
* processing or the message processing
*/
public void writePayloadsToMessage(byte[] data, AS2Message message, Properties header) throws Exception {
ByteArrayOutputStream payloadOut = new ByteArrayOutputStream();
MimeMessage testMessage = new MimeMessage(Session.getInstance(System.getProperties()), new ByteArrayInputStream(data));
//multiple attachments?
if (testMessage.isMimeType("multipart/*")) {
this.writePayloadsToMessage(testMessage, message, header);
return;
}
InputStream payloadIn = null;
AS2Info info = message.getAS2Info();
if (info instanceof AS2MessageInfo
&& info.getSignType() == AS2Message.SIGNATURE_NONE
&& ((AS2MessageInfo) info).getCompressionType() == AS2Message.COMPRESSION_NONE) {
payloadIn = new ByteArrayInputStream(data);
} else if (testMessage.getSize() > 0) {
payloadIn = testMessage.getInputStream();
} else {
payloadIn = new ByteArrayInputStream(data);
}
this.copyStreams(payloadIn, payloadOut);
payloadOut.flush();
payloadOut.close();
byte[] payloadData = payloadOut.toByteArray();
AS2Payload as2Payload = new AS2Payload();
as2Payload.setData(payloadData);
String contentIdHeader = header.getProperty("content-id");
if (contentIdHeader != null) {
as2Payload.setContentId(contentIdHeader);
}
String contentTypeHeader = header.getProperty("content-type");
if (contentTypeHeader != null) {
as2Payload.setContentType(contentTypeHeader);
}
try {
as2Payload.setOriginalFilename(testMessage.getFileName());
} catch (MessagingException e) {
if (this.logger != null) {
this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
new Object[]{
info.getMessageId(),
e.getMessage(),}), info);
}
}
if (as2Payload.getOriginalFilename() == null) {
String filenameHeader = header.getProperty("content-disposition");
if (filenameHeader != null) {
//test part for convinience: extract file name
MimeBodyPart filenamePart = new MimeBodyPart();
filenamePart.setHeader("content-disposition", filenameHeader);
try {
as2Payload.setOriginalFilename(filenamePart.getFileName());
} catch (MessagingException e) {
if (this.logger != null) {
this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
new Object[]{
info.getMessageId(),
e.getMessage(),}), info);
}
}
}
}
message.addPayload(as2Payload);
}
/**Writes a passed payload part to the passed message object.
*/
public void writePayloadsToMessage(Part payloadPart, AS2Message message, Properties header) throws Exception {
List<Part> attachmentList = new ArrayList<Part>();
AS2Info info = message.getAS2Info();
if (!info.isMDN()) {
AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
if (payloadPart.isMimeType("multipart/*")) {
//check if it is a CEM
if (payloadPart.getContentType().toLowerCase().contains("application/ediint-cert-exchange+xml")) {
messageInfo.setMessageType(AS2Message.MESSAGETYPE_CEM);
if (this.logger != null) {
this.logger.log(Level.FINE, this.rb.getResourceString("found.cem",
new Object[]{
messageInfo.getMessageId(),
message
}), info);
}
}
ByteArrayOutputStream mem = new ByteArrayOutputStream();
payloadPart.writeTo(mem);
mem.flush();
mem.close();
MimeMultipart multipart = new MimeMultipart(
new ByteArrayDataSource(mem.toByteArray(), payloadPart.getContentType()));
//add all attachments to the message
for (int i = 0; i < multipart.getCount(); i++) {
//its possible that one of the bodyparts is the signature (for compressed/signed messages), skip the signature
if (!multipart.getBodyPart(i).getContentType().toLowerCase().contains("pkcs7-signature")) {
attachmentList.add(multipart.getBodyPart(i));
}
}
} else {
attachmentList.add(payloadPart);
}
} else {
//its a MDN, write whole part
attachmentList.add(payloadPart);
}
//write the parts
for (Part attachmentPart : attachmentList) {
ByteArrayOutputStream payloadOut = new ByteArrayOutputStream();
InputStream payloadIn = attachmentPart.getInputStream();
this.copyStreams(payloadIn, payloadOut);
payloadOut.flush();
payloadOut.close();
byte[] data = payloadOut.toByteArray();
AS2Payload as2Payload = new AS2Payload();
as2Payload.setData(data);
String[] contentIdHeader = attachmentPart.getHeader("content-id");
if (contentIdHeader != null && contentIdHeader.length > 0) {
as2Payload.setContentId(contentIdHeader[0]);
}
String[] contentTypeHeader = attachmentPart.getHeader("content-type");
if (contentTypeHeader != null && contentTypeHeader.length > 0) {
as2Payload.setContentType(contentTypeHeader[0]);
}
try {
as2Payload.setOriginalFilename(payloadPart.getFileName());
} catch (MessagingException e) {
if (this.logger != null) {
this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
new Object[]{
info.getMessageId(),
e.getMessage(),}), info);
}
}
if (as2Payload.getOriginalFilename() == null) {
String filenameheader = header.getProperty("content-disposition");
if (filenameheader != null) {
//test part for convinience: extract file name
MimeBodyPart filenamePart = new MimeBodyPart();
filenamePart.setHeader("content-disposition", filenameheader);
try {
as2Payload.setOriginalFilename(filenamePart.getFileName());
} catch (MessagingException e) {
if (this.logger != null) {
this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
new Object[]{
info.getMessageId(),
e.getMessage(),}), info);
}
}
}
}
message.addPayload(as2Payload);
}
}
/**Computes the received content MIC and writes it to the message info object
*/
public void computeReceivedContentMIC(byte[] rawMessageData, AS2Message message, Part partWithHeader, String contentType) throws Exception {
AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
boolean encrypted = messageInfo.getEncryptionType() != AS2Message.ENCRYPTION_NONE;
boolean signed = messageInfo.getSignType() != AS2Message.SIGNATURE_NONE;
boolean compressed = messageInfo.getCompressionType() != AS2Message.COMPRESSION_NONE;
BCCryptoHelper helper = new BCCryptoHelper();
String sha1digestOID = helper.convertAlgorithmNameToOID(BCCryptoHelper.ALGORITHM_SHA1);
//compute the MIC
if (signed) {
//compute the content-type for the signed part.
//If the message was not encrypted the content-type should simply be taken from the header
//else we have to look into the part
String singedPartContentType = null;
if (!encrypted) {
singedPartContentType = contentType;
} else {
InputStream dataIn = message.getDecryptedRawDataInputStream();
MimeBodyPart contentTypeTempPart = new MimeBodyPart(dataIn);
dataIn.close();
singedPartContentType = contentTypeTempPart.getContentType();
}
//ANY signed data
//4.1 MIC Calculation For Signed Message
//For any signed message, the MIC to be returned is calculated over
//the same data that was signed in the original message as per [AS1].
//The signed content will be a mime bodypart that contains either
//compressed or uncompressed data.
MimeBodyPart signedPart = new MimeBodyPart();
signedPart.setDataHandler(new DataHandler(new ByteArrayDataSource(message.getDecryptedRawData(), contentType)));
signedPart.setHeader("Content-Type", singedPartContentType);
String digestOID = helper.getDigestAlgOIDFromSignature(signedPart);
signedPart = null;
String mic = helper.calculateMIC(partWithHeader, digestOID);
String digestAlgorithmName = helper.convertOIDToAlgorithmName(digestOID);
messageInfo.setReceivedContentMIC(mic + ", " + digestAlgorithmName);
} else if (!signed && !compressed && !encrypted) {
//uncompressed, unencrypted, unsigned: plaintext mic
//http://tools.ietf.org/html/draft-ietf-ediint-compression-12
//4.3 MIC Calculation For Unencrypted, Unsigned Message
//For unsigned, unencrypted messages, the MIC is calculated
//over the uncompressed data content including all MIME header
//fields and any applied Content-Transfer-Encoding.
String mic = helper.calculateMIC(rawMessageData, sha1digestOID);
messageInfo.setReceivedContentMIC(mic + ", sha1");
} else if (!signed && compressed && !encrypted) {
//compressed, unencrypted, unsigned: uncompressed data mic
//http://tools.ietf.org/html/draft-ietf-ediint-compression-12
//4.3 MIC Calculation For Unencrypted, Unsigned Message
//For unsigned, unencrypted messages, the MIC is calculated
//over the uncompressed data content including all MIME header
//fields and any applied Content-Transfer-Encoding.
String mic = helper.calculateMIC(message.getDecryptedRawData(), sha1digestOID);
messageInfo.setReceivedContentMIC(mic + ", sha1");
} else if (!signed && encrypted) {
//http://tools.ietf.org/html/draft-ietf-ediint-compression-12
//4.2 MIC Calculation For Encrypted, Unsigned Message
//For encrypted, unsigned messages, the MIC to be returned is
//calculated over the uncompressed data content including all
//MIME header fields and any applied Content-Transfer-Encoding.
String mic = helper.calculateMIC(message.getDecryptedRawData(), sha1digestOID);
messageInfo.setReceivedContentMIC(mic + ", sha1");
} else {
//this should never happen:
String mic = helper.calculateMIC(partWithHeader, sha1digestOID);
messageInfo.setReceivedContentMIC(mic + ", sha1");
}
}
/**Returns a compressed part of this container if it exists, else null. If the container itself
*is compressed it is returned.
*/
public Part getCompressedEmbeddedPart(Part part) throws MessagingException, IOException {
if (this.contentTypeIndicatesCompression(part.getContentType())) {
return (part);
}
if (part.isMimeType("multipart/*")) {
Multipart multiPart = (Multipart) part.getContent();
int count = multiPart.getCount();
for (int i = 0; i < count; i++) {
BodyPart bodyPart = multiPart.getBodyPart(i);
Part compressedEmbeddedPart = this.getCompressedEmbeddedPart(bodyPart);
if (compressedEmbeddedPart != null) {
return (compressedEmbeddedPart);
}
}
}
return (null);
}
/**Returns the signed part of the passed data or null if the data is not detected to be signed*/
public Part getSignedPart(byte[] data, String contentType) throws Exception {
BCCryptoHelper helper = new BCCryptoHelper();
MimeBodyPart possibleSignedPart = new MimeBodyPart();
possibleSignedPart.setDataHandler(new DataHandler(new ByteArrayDataSource(data, contentType)));
possibleSignedPart.setHeader("content-type", contentType);
return (helper.getSignedEmbeddedPart(possibleSignedPart));
}
/**Verifies the signature of the passed signed part*/
public MimeBodyPart verifySignedPart(Part signedPart, byte[] data, String contentType, X509Certificate certificate) throws Exception {
BCCryptoHelper helper = new BCCryptoHelper();
String signatureTransferEncoding = null;
MimeMultipart checkPart = (MimeMultipart) signedPart.getContent();
//it is sure that it is a signed part: set the type to multipart if the
//parser has problems parsing it. Don't know why sometimes a parsing fails for
//MimeBodyPart. This check looks if the parser is able to find more than one subpart
if (checkPart.getCount() == 1) {
MimeMultipart multipart = new MimeMultipart(
new ByteArrayDataSource(data, contentType));
MimeMessage possibleSignedMessage = new MimeMessage(Session.getInstance(System.getProperties(), null));
possibleSignedMessage.setContent(multipart, multipart.getContentType());
possibleSignedMessage.saveChanges();
//overwrite the formerly found signed part
signedPart = helper.getSignedEmbeddedPart(possibleSignedMessage);
}
//get the content encoding of the signature
MimeMultipart signedMultiPart = (MimeMultipart) signedPart.getContent();
//body part 1 is always the signature
String encodingHeader[] = signedMultiPart.getBodyPart(1).getHeader("Content-Transfer-Encoding");
if (encodingHeader != null) {
signatureTransferEncoding = encodingHeader[0];
}
return (helper.verify(signedPart, signatureTransferEncoding, certificate));
}
/**Verifies the signature of the passed message. If the transfer mode is unencrypted/unsigned, a new Bodypart will be constructed
*@return the payload part, this is important to compute the MIC later
*/
private Part verifySignature(AS2Message message, Partner sender, String contentType) throws Exception {
if (this.certificateManagerSignature == null) {
throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
"AS2MessageParser.verifySignature: pass a certification manager for the signature before calling verifySignature()", message);
}
AS2Info as2Info = message.getAS2Info();
if (!as2Info.isMDN()) {
AS2MessageInfo messageInfo = (AS2MessageInfo) as2Info;
if (messageInfo.getEncryptionType() != AS2Message.ENCRYPTION_NONE) {
InputStream memIn = message.getDecryptedRawDataInputStream();
MimeBodyPart testPart = new MimeBodyPart(memIn);
memIn.close();
contentType = testPart.getContentType();
}
}
Part signedPart = this.getSignedPart(message.getDecryptedRawData(), contentType);
//part is NOT signed but is defined to be signed
if (signedPart == null) {
as2Info.setSignType(AS2Message.SIGNATURE_NONE);
if (as2Info.isMDN()) {
this.logger.log(Level.INFO, this.rb.getResourceString("mdn.notsigned", as2Info.getMessageId()), as2Info);
//MDN is not signed but should be signed
if (sender.isSignedMDN()) {
this.logger.log(Level.SEVERE, this.rb.getResourceString("mdn.unsigned.error",
new Object[]{
as2Info.getMessageId(),
sender.getName(),}), as2Info);
}
} else {
this.logger.log(Level.INFO, this.rb.getResourceString("msg.notsigned", as2Info.getMessageId()), as2Info);
}
if (!as2Info.isMDN() && sender.getSignType() != AS2Message.SIGNATURE_NONE) {
throw new AS2Exception(AS2Exception.INSUFFICIENT_SECURITY_ERROR,
"Incoming messages from AS2 partner " + sender.getAS2Identification() + " are defined to be signed.",
message);
}
//if the message has been unsigned it is required to set a new datasource
MimeBodyPart unsignedPart = new MimeBodyPart();
unsignedPart.setDataHandler(new DataHandler(new ByteArrayDataSource(message.getDecryptedRawData(), contentType)));
unsignedPart.setHeader("content-type", contentType);
return (unsignedPart);
} else {
//it is definitly a signed mdn
if (as2Info.isMDN()) {
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("mdn.signed", as2Info.getMessageId()), as2Info);
}
as2Info.setSignType(this.getDigestFromSignature(signedPart));
//MDN is signed but shouldn't be signed'
if (!sender.isSignedMDN()) {
if (this.logger != null) {
this.logger.log(Level.WARNING, this.rb.getResourceString("mdn.signed.error",
new Object[]{
as2Info.getMessageId(),
sender.getName(),}), as2Info);
}
}
} else {
//its no MDN, its a AS2 message
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("msg.signed", as2Info.getMessageId()), as2Info);
}
int signDigest = this.getDigestFromSignature(signedPart);
String digest = null;
if (signDigest == AS2Message.SIGNATURE_SHA1) {
digest = "SHA1";
} else if (signDigest == AS2Message.SIGNATURE_MD5) {
digest = "MD5";
}
as2Info.setSignType(signDigest);
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("signature.analyzed.digest",
new Object[]{
as2Info.getMessageId(), digest
}), as2Info);
}
}
}
MimeBodyPart payloadPart = null;
try {
String signAlias = this.certificateManagerSignature.getAliasByFingerprint(sender.getSignFingerprintSHA1());
payloadPart = this.verifySignedPartUsingAlias(message, signAlias, signedPart, contentType);
} catch (AS2Exception e) {
//retry to verify the signature with the second certificate if this is possible
String secondAlias = this.certificateManagerSignature.getAliasByFingerprint(sender.getSignFingerprintSHA1(2));
if (secondAlias != null) {
payloadPart = this.verifySignedPartUsingAlias(message, secondAlias, signedPart, contentType);
} else {
throw e;
}
}
return (payloadPart);
}
private MimeBodyPart verifySignedPartUsingAlias(AS2Message message, String alias, Part signedPart, String contentType) throws Exception {
AS2Info info = message.getAS2Info();
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("signature.using.alias",
new Object[]{
info.getMessageId(), alias
}), info);
}
X509Certificate certificate = this.certificateManagerSignature.getX509Certificate(alias);
MimeBodyPart payloadPart = null;
try {
payloadPart = this.verifySignedPart(signedPart, message.getDecryptedRawData(), contentType, certificate);
} catch (Exception e) {
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("signature.failure",
new Object[]{
info.getMessageId(),
e.getMessage()
}), info);
}
throw new AS2Exception(AS2Exception.AUTHENTIFICATION_ERROR,
"Error verifying the senders digital signature: " + e.getMessage() + ".",
message);
}
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("signature.ok", info.getMessageId()), info);
}
return (payloadPart);
}
/*Returns the digest of the signature, as constant of AS2Message
*/
public int getDigestFromSignature(Part signedPart) throws Exception {
BCCryptoHelper helper = new BCCryptoHelper();
String digestOID = helper.getDigestAlgOIDFromSignature(signedPart);
String as2Digest = helper.convertOIDToAlgorithmName(digestOID);
if (as2Digest.equalsIgnoreCase(BCCryptoHelper.ALGORITHM_SHA1)) {
return (AS2Message.SIGNATURE_SHA1);
}
if (as2Digest.equalsIgnoreCase(BCCryptoHelper.ALGORITHM_MD5)) {
return (AS2Message.SIGNATURE_MD5);
}
//should never happen because unknown algorithms are thrown already by the conversion method
return (-1);
}
/**Returns if the content type indicates a message encryption*/
public boolean contentTypeIndicatesEncryption(String contentType) {
return (contentType.toLowerCase().contains("application/pkcs7-mime"));
}
/**Returns if the content type indicates message compression*/
public boolean contentTypeIndicatesCompression(String contentType) {
return (contentType.toLowerCase().contains("compressed-data"));
}
/**Decrypts the data of a message with all given certificates etc
* @param info MessageInfo, the encryption algorith will be stored in the encryption type of this info
* @param rawMessageData encrypted data, will be decrypted
* @param contentType contentType of the data
* @param privateKey receivers private key
* @param certificate receivers certificate
*/
public byte[] decryptData(AS2Message message, byte[] data, String contentType, PrivateKey privateKeyReceiver, X509Certificate certificateReceiver, String receiverCryptAlias) throws Exception {
AS2MessageInfo info = (AS2MessageInfo) message.getAS2Info();
MimeBodyPart encryptedBody = new MimeBodyPart();
encryptedBody.setHeader("content-type", contentType);
encryptedBody.setDataHandler(new DataHandler(new ByteArrayDataSource(data, contentType)));
RecipientId recipientId = new JceKeyTransRecipientId(certificateReceiver);
SMIMEEnveloped enveloped = new SMIMEEnveloped(encryptedBody);
BCCryptoHelper helper = new BCCryptoHelper();
String algorithm = helper.convertOIDToAlgorithmName(enveloped.getEncryptionAlgOID());
if (algorithm.equals(BCCryptoHelper.ALGORITHM_3DES)) {
info.setEncryptionType(AS2Message.ENCRYPTION_3DES);
} else if (algorithm.equals(BCCryptoHelper.ALGORITHM_DES)) {
info.setEncryptionType(AS2Message.ENCRYPTION_DES);
} else if (algorithm.equals(BCCryptoHelper.ALGORITHM_RC2)) {
info.setEncryptionType(AS2Message.ENCRYPTION_RC2_UNKNOWN);
} else if (algorithm.equals(BCCryptoHelper.ALGORITHM_AES_128)) {
info.setEncryptionType(AS2Message.ENCRYPTION_AES_128);
} else if (algorithm.equals(BCCryptoHelper.ALGORITHM_AES_192)) {
info.setEncryptionType(AS2Message.ENCRYPTION_AES_192);
} else if (algorithm.equals(BCCryptoHelper.ALGORITHM_AES_256)) {
info.setEncryptionType(AS2Message.ENCRYPTION_AES_256);
} else if (algorithm.equals(BCCryptoHelper.ALGORITHM_RC4)) {
info.setEncryptionType(AS2Message.ENCRYPTION_RC4_UNKNOWN);
} else {
info.setEncryptionType(AS2Message.ENCRYPTION_UNKNOWN_ALGORITHM);
}
RecipientInformationStore recipients = enveloped.getRecipientInfos();
enveloped = null;
encryptedBody = null;
RecipientInformation recipient = recipients.get(recipientId);
if (recipient == null) {
//give some details about the required and used cert for the decryption
Collection recipientList = recipients.getRecipients();
Iterator iterator = recipientList.iterator();
while (iterator.hasNext()) {
RecipientInformation recipientInfo = (RecipientInformation) iterator.next();
if (this.logger != null) {
this.logger.log(Level.SEVERE, this.rb.getResourceString("decryption.inforequired",
new Object[]{
info.getMessageId(),
recipientInfo.getRID()
}),
info);
}
}
if (this.logger != null) {
this.logger.log(Level.SEVERE, this.rb.getResourceString("decryption.infoassigned",
new Object[]{
info.getMessageId(),
receiverCryptAlias,
recipientId
}),
info);
}
throw new AS2Exception(AS2Exception.AUTHENTIFICATION_ERROR,
"Error decrypting the message: Recipient certificate does not match.", message);
}
//Streamed decryption. Its also possible to use in memory decryption using getContent but that uses
//far more memory.
InputStream contentStream = recipient.getContentStream(
new JceKeyTransEnvelopedRecipient(privateKeyReceiver).setProvider("BC")).getContentStream();
//InputStream contentStream = recipient.getContentStream(privateKeyReceiver, "BC").getContentStream();
//threshold set to 20 MB: if the data is less then 20MB perform the operaion in memory else stream to disk
DeferredFileOutputStream decryptedOutput = new DeferredFileOutputStream(20 * 1024 * 1024, "as2decryptdata_", ".mem", null);
this.copyStreams(contentStream, decryptedOutput);
decryptedOutput.flush();
decryptedOutput.close();
contentStream.close();
byte[] decryptedData = null;
//size of the data was < than the threshold
if (decryptedOutput.isInMemory()) {
decryptedData = decryptedOutput.getData();
} else {
//data has been written to a temp file: reread and return
ByteArrayOutputStream memOut = new ByteArrayOutputStream();
decryptedOutput.writeTo(memOut);
memOut.flush();
memOut.close();
//finally delete the temp file
boolean deleted = decryptedOutput.getFile().delete();
decryptedData = memOut.toByteArray();
}
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("decryption.done.alias",
new Object[]{
info.getMessageId(),
receiverCryptAlias,
this.rbMessage.getResourceString("encryption." + info.getEncryptionType())
}),
info);
}
return (decryptedData);
}
/**Decrypts the passed data and returns it. Will return the original data
*if it is not marked as encrypted
*/
private byte[] decryptMessage(AS2Message message, byte[] data, String contentType, Partner sender, Partner receiver) throws AS2Exception {
if (this.certificateManagerEncryption == null) {
throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
"AS2MessageParser.decryptMessage: pass a certification manager for the encryption before calling decryptMessage()", message);
}
try {
AS2MessageInfo info = (AS2MessageInfo) message.getAS2Info();
//first check if the message is decrypted.
if (!this.contentTypeIndicatesEncryption(contentType) || this.contentTypeIndicatesCompression(contentType)) {
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("msg.notencrypted", info.getMessageId()), info);
}
info.setEncryptionType(AS2Message.ENCRYPTION_NONE);
//encryption expected?
if (sender.getEncryptionType() != AS2Message.ENCRYPTION_NONE) {
throw new AS2Exception(AS2Exception.INSUFFICIENT_SECURITY_ERROR,
"Incoming messages from AS2 partner " + sender.getAS2Identification() + " are defined to be encrypted.",
message);
}
return (data);
}
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("msg.encrypted", info.getMessageId()), info);
}
try {
String cryptAlias = this.certificateManagerEncryption.getAliasByFingerprint(receiver.getCryptFingerprintSHA1());
X509Certificate certificate = this.certificateManagerEncryption.getX509Certificate(cryptAlias);
//receiver priv key
PrivateKey privateKey = this.certificateManagerEncryption.getPrivateKey(cryptAlias);
return (this.decryptData(message, data, contentType, privateKey, certificate, cryptAlias));
} catch (Exception e) {
//fallback: try second certificate if it exists
String cryptAlias = this.certificateManagerEncryption.getAliasByFingerprint(receiver.getCryptFingerprintSHA1(2));
if (cryptAlias != null) {
X509Certificate certificate = this.certificateManagerEncryption.getX509Certificate(cryptAlias);
//receiver priv key
PrivateKey privateKey = this.certificateManagerEncryption.getPrivateKey(cryptAlias);
return (this.decryptData(message, data, contentType, privateKey, certificate, cryptAlias));
} else {
throw e;
}
}
} catch (AS2Exception e) {
throw e;
} catch (Throwable e) {
e.printStackTrace();
throw new AS2Exception(AS2Exception.DECRYPTION_ERROR, e.getMessage(), message);
}
}
/**Looks if the data is compressed and decompresses it if necessary
*/
public Part decompressData(Part part, AS2Message message) throws Exception {
Part compressedPart = this.getCompressedEmbeddedPart(part);
if (compressedPart == null) {
return (part);
}
SMIMECompressed compressed = null;
if (compressedPart instanceof MimeBodyPart) {
compressed = new SMIMECompressed((MimeBodyPart) compressedPart);
} else {
compressed = new SMIMECompressed((MimeMessage) compressedPart);
}
byte[] decompressedData = compressed.getContent();
((AS2MessageInfo) message.getAS2Info()).setCompressionType(AS2Message.COMPRESSION_ZLIB);
if (this.logger != null) {
this.logger.log(Level.INFO, this.rb.getResourceString("data.compressed.expanded",
new Object[]{
message.getAS2Info().getMessageId(), AS2Tools.getDataSizeDisplay(part.getSize()),
AS2Tools.getDataSizeDisplay(decompressedData.length)
}), message.getAS2Info());
}
ByteArrayInputStream memIn = new ByteArrayInputStream(decompressedData);
MimeBodyPart uncompressedPayload = new MimeBodyPart(memIn);
memIn.close();
return (uncompressedPayload);
}
/**Copies all data from one stream to another*/
private void copyStreams(InputStream in, OutputStream out) throws IOException {
BufferedInputStream inStream = new BufferedInputStream(in);
BufferedOutputStream outStream = new BufferedOutputStream(out);
//copy the contents to an output stream
byte[] buffer = new byte[2048];
int read = 0;
//a read of 0 must be allowed, sometimes it takes time to
//extract data from the input
while (read != -1) {
read = inStream.read(buffer);
if (read > 0) {
outStream.write(buffer, 0, read);
}
}
outStream.flush();
}
}