From 25e803bf0c30f44ba57deee050b977b1532bb92c Mon Sep 17 00:00:00 2001 From: "prasad%netscape.com" Date: Mon, 24 Aug 1998 22:22:59 +0000 Subject: [PATCH] adding new sources --- .../MIME/examples/message_part/ReadMe.txt | 13 + .../message_part/TestMessagePart.java | 180 ++ .../MIME/examples/msg_extern_body/ReadMe.txt | 13 + .../TestExtBodyMessagePart.java | 110 + .../MIME/examples/multi_part/ReadMe.txt | 13 + .../multi_part/TestMultiPartMessage.java | 249 ++ msgsdk/java/protocol/MIME/src/ByteBuffer.java | 312 ++ msgsdk/java/protocol/MIME/src/ByteString.java | 501 +++ msgsdk/java/protocol/MIME/src/Header.java | 215 ++ .../java/protocol/MIME/src/MIMEBasicPart.java | 1050 ++++++ .../java/protocol/MIME/src/MIMEBodyPart.java | 281 ++ .../java/protocol/MIME/src/MIMEDataSink.java | 256 ++ .../protocol/MIME/src/MIMEDynamicParser.java | 123 + .../java/protocol/MIME/src/MIMEException.java | 57 + msgsdk/java/protocol/MIME/src/MIMEHelper.java | 2243 +++++++++++++ .../java/protocol/MIME/src/MIMEMessage.java | 931 ++++++ .../protocol/MIME/src/MIMEMessagePart.java | 659 ++++ .../java/protocol/MIME/src/MIMEMultiPart.java | 911 ++++++ msgsdk/java/protocol/MIME/src/MIMEParser.java | 2834 +++++++++++++++++ msgsdk/java/protocol/MIME/src/Makefile | 73 + .../java/protocol/MIME/src/fileMIMEType.java | 54 + msgsdk/java/protocol/MIME/src/log.java | 119 + msgsdk/java/protocol/MIME/testapp/ReadMe.txt | 32 + .../protocol/MIME/testapp/myDataSink.java | 287 ++ .../java/protocol/MIME/testapp/testApp.java | 368 +++ .../protocol/MIME/testapp/testAppStr.java | 370 +++ .../protocol/MIME/testapp/testAppStrLoop.java | 386 +++ .../java/protocol/Messaging/src/Common.java | 117 + msgsdk/java/protocol/Messaging/src/IO.java | 633 ++++ msgsdk/java/protocol/Messaging/src/Makefile | 54 + msgsdk/java/protocol/SMTP/src/ISMTPSink.java | 270 ++ msgsdk/java/protocol/SMTP/src/Makefile | 57 + msgsdk/java/protocol/SMTP/src/SMTPClient.java | 1418 +++++++++ .../java/protocol/SMTP/src/SMTPException.java | 55 + .../SMTP/src/SMTPServerException.java | 67 + msgsdk/java/protocol/SMTP/src/SMTPSink.java | 273 ++ msgsdk/java/protocol/SMTP/test/SMTPTest.java | 108 + .../java/protocol/SMTP/test/SMTPTestSink.java | 195 ++ 38 files changed, 15887 insertions(+) create mode 100644 msgsdk/java/protocol/MIME/examples/message_part/ReadMe.txt create mode 100644 msgsdk/java/protocol/MIME/examples/message_part/TestMessagePart.java create mode 100644 msgsdk/java/protocol/MIME/examples/msg_extern_body/ReadMe.txt create mode 100644 msgsdk/java/protocol/MIME/examples/msg_extern_body/TestExtBodyMessagePart.java create mode 100644 msgsdk/java/protocol/MIME/examples/multi_part/ReadMe.txt create mode 100644 msgsdk/java/protocol/MIME/examples/multi_part/TestMultiPartMessage.java create mode 100644 msgsdk/java/protocol/MIME/src/ByteBuffer.java create mode 100644 msgsdk/java/protocol/MIME/src/ByteString.java create mode 100644 msgsdk/java/protocol/MIME/src/Header.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEBasicPart.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEBodyPart.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEDataSink.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEDynamicParser.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEException.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEHelper.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEMessage.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEMessagePart.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEMultiPart.java create mode 100644 msgsdk/java/protocol/MIME/src/MIMEParser.java create mode 100644 msgsdk/java/protocol/MIME/src/Makefile create mode 100644 msgsdk/java/protocol/MIME/src/fileMIMEType.java create mode 100644 msgsdk/java/protocol/MIME/src/log.java create mode 100644 msgsdk/java/protocol/MIME/testapp/ReadMe.txt create mode 100644 msgsdk/java/protocol/MIME/testapp/myDataSink.java create mode 100644 msgsdk/java/protocol/MIME/testapp/testApp.java create mode 100644 msgsdk/java/protocol/MIME/testapp/testAppStr.java create mode 100644 msgsdk/java/protocol/MIME/testapp/testAppStrLoop.java create mode 100644 msgsdk/java/protocol/Messaging/src/Common.java create mode 100644 msgsdk/java/protocol/Messaging/src/IO.java create mode 100644 msgsdk/java/protocol/Messaging/src/Makefile create mode 100644 msgsdk/java/protocol/SMTP/src/ISMTPSink.java create mode 100644 msgsdk/java/protocol/SMTP/src/Makefile create mode 100644 msgsdk/java/protocol/SMTP/src/SMTPClient.java create mode 100644 msgsdk/java/protocol/SMTP/src/SMTPException.java create mode 100644 msgsdk/java/protocol/SMTP/src/SMTPServerException.java create mode 100644 msgsdk/java/protocol/SMTP/src/SMTPSink.java create mode 100644 msgsdk/java/protocol/SMTP/test/SMTPTest.java create mode 100644 msgsdk/java/protocol/SMTP/test/SMTPTestSink.java diff --git a/msgsdk/java/protocol/MIME/examples/message_part/ReadMe.txt b/msgsdk/java/protocol/MIME/examples/message_part/ReadMe.txt new file mode 100644 index 00000000000..6fee38aa30c --- /dev/null +++ b/msgsdk/java/protocol/MIME/examples/message_part/ReadMe.txt @@ -0,0 +1,13 @@ +This directory contains an example program that demonstrates the use of the Netscape +Messaging MIME Encoder java API. + +To compile and run: +------------------- + +(1) Make sure CLASSPATH includes the msg-sdk jar file (proapi.jar). + When installed proapi.jar is located at /packages/proapi.jar. + Make sure CLASSPATH also includes the directory you are compiling in. + +(2) Compile: javac *.java +(3) Run: java + diff --git a/msgsdk/java/protocol/MIME/examples/message_part/TestMessagePart.java b/msgsdk/java/protocol/MIME/examples/message_part/TestMessagePart.java new file mode 100644 index 00000000000..1c80144f894 --- /dev/null +++ b/msgsdk/java/protocol/MIME/examples/message_part/TestMessagePart.java @@ -0,0 +1,180 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.*; + + +/** + * Example program for MIME Encoder API. + * Demonstrates how to create messages with MessagePart content + */ +public class TestMessagePart +{ + private static final int MultiPart = 10; + private static final int MessagePart = 11; + private static String tmpdir; + + public TestMessagePart () + { + String sep = System.getProperty ("path.separator"); + + if (sep.equals (";")) + tmpdir = "C:\\temp\\"; + else + tmpdir = "/tmp/"; + } + + public static void setOtherHeaders (MIMEMessage mmsg) + { + try + { + mmsg.setHeader ("From", "Prasad Yendluri "); + mmsg.setHeader ("To", "Prasad Yendluri "); + mmsg.setHeader ("CC", "Prasad Yendluri "); + mmsg.setHeader ("Reply-To", "Prasad Yendluri "); + mmsg.setHeader ("X-MyTest-Header", "Test-Header-1"); + } + catch (Exception e) + { + System.out.println ("setOtherHeader> Caught exception: " + e); + } + } + + + // create a text-part + public MIMEBasicPart createTextPart (String inText) + { + MIMEBasicPart l_mbp; + + try + { + l_mbp = new MIMEBasicPart (MIMEBasicPart.TEXT); + l_mbp.setContentSubType ("plain"); + l_mbp.setContentTypeParams ("charset=us-ascii"); + l_mbp.setContentID("Text_BasicPart_ContentID"); + //l_mbp.setContentEncoding(MIMEBodyPart.E7BIT); + l_mbp.setContentDisposition(MIMEBodyPart.INLINE); + //l_mbp.setContentDisposition(MIMEBodyPart.ATTACHMENT); // if you like + l_mbp.setContentDispParams("TextPart Disp-Params"); + l_mbp.setContentDispParams("Some_Params"); + l_mbp.setHeader ("X-TextPartHeader-1", "myValue-1"); + l_mbp.setHeader ("X-TextPartHeader-2", "myValue-2"); + l_mbp.setBodyData (inText); + + return l_mbp; + } + catch (Exception e) + { + System.out.println ("createTextPart Caught exception: " + e); + return null; + } + } + + // create a message + public MIMEMessage createMessage () throws MIMEException + { + MIMEMessage l_msg; + + // create a blank message + l_msg = new MIMEMessage(); + + // create a text part + MIMEBasicPart l_txtPart = createTextPart ("Hello this is a simple text part"); + + // set the text part as body of the message + l_msg.setBody (l_txtPart, false); // false implies don't clone the part + + // set some headers on the message + l_msg.setHeader ("Subject", "Inner Text Message"); + l_msg.setHeader ("From", "Prasad Yendluri "); + l_msg.setHeader ("To", "Prasad Yendluri "); + l_msg.setHeader ("Reply-To", "Prasad Yendluri "); + l_msg.setHeader ("X-Hdr-Inner", "Some Value"); + + // we are done. Return the created message + return l_msg; + } + + // create a message part + public MIMEMessagePart createMessagePart () + { + MIMEMessagePart l_msgpart; + + try + { + // first create the message that goes in the message part + MIMEMessage l_msg = createMessage (); + + // Create a message part w/ above message + l_msgpart = new MIMEMessagePart (l_msg); + + // set any attributes on the messagePart + l_msgpart.setContentDescription ("This is a forwarded message"); + l_msgpart.setContentDisposition (MIMEBodyPart.INLINE); + l_msgpart.setContentEncoding (MIMEBodyPart.E7BIT); // default E7BIT + //l_msgpart.setContentSubType ("rfc822"); // default rfc822 + + return l_msgpart; + + } + catch (Exception e) + { + System.out.println ("createMsgPartExt Caught exception: " + e); + return null; + } + } + + public static void main (String[] args) + { + MIMEMessage mmsg; + MIMEMessagePart mmsgpart; + FileOutputStream fos; + TestMessagePart tm = new TestMessagePart (); + + try + { + // Create a message part + mmsgpart = tm.createMessagePart (); + + // Create a new Message with this messagePart. + MIMEMessage msg = new MIMEMessage (); + + // set the above messagePart as body of the message + msg.setBody (mmsgpart, false); + + // Set headers of the new Message + msg.setHeader ("Subject", "Test Message w/ MessagePart Body"); + setOtherHeaders (msg); + + // MIME encode the created message + fos = new FileOutputStream (tmpdir + "MsgWithMessagePart.out"); + msg.putByteStream (fos); + } + catch (Exception e) + { + System.out.println ("Caught exception: " + e); + } + + } // main() + +} // TestMessagePart diff --git a/msgsdk/java/protocol/MIME/examples/msg_extern_body/ReadMe.txt b/msgsdk/java/protocol/MIME/examples/msg_extern_body/ReadMe.txt new file mode 100644 index 00000000000..6fee38aa30c --- /dev/null +++ b/msgsdk/java/protocol/MIME/examples/msg_extern_body/ReadMe.txt @@ -0,0 +1,13 @@ +This directory contains an example program that demonstrates the use of the Netscape +Messaging MIME Encoder java API. + +To compile and run: +------------------- + +(1) Make sure CLASSPATH includes the msg-sdk jar file (proapi.jar). + When installed proapi.jar is located at /packages/proapi.jar. + Make sure CLASSPATH also includes the directory you are compiling in. + +(2) Compile: javac *.java +(3) Run: java + diff --git a/msgsdk/java/protocol/MIME/examples/msg_extern_body/TestExtBodyMessagePart.java b/msgsdk/java/protocol/MIME/examples/msg_extern_body/TestExtBodyMessagePart.java new file mode 100644 index 00000000000..50c912d94a9 --- /dev/null +++ b/msgsdk/java/protocol/MIME/examples/msg_extern_body/TestExtBodyMessagePart.java @@ -0,0 +1,110 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.*; + + +/** + * Example program for MIME Encoder API. + * Demonstrates how to create messages with message/external-body content. + */ +public class TestExtBodyMessagePart +{ + private static final int MultiPart = 10; + private static final int MessagePart = 11; + private static String tmpdir; + + public TestExtBodyMessagePart () + { + String sep = System.getProperty ("path.separator"); + + if (sep.equals (";")) + tmpdir = "C:\\temp\\"; + else + tmpdir = "/tmp/"; + } + + public static void setOtherHeaders (MIMEMessage mmsg) + { + try { + mmsg.setHeader ("From", "Prasad Yendluri "); + mmsg.setHeader ("To", "Prasad Yendluri "); + mmsg.setHeader ("CC", "Prasad Yendluri "); + mmsg.setHeader ("Reply-To", "Prasad Yendluri "); + mmsg.setHeader ("X-MyTest-Header", "Test-Header-1"); + } + catch (Exception e) + { + System.out.println ("setOtherHeader> Caught exception: " + e); + } + } + + + public static void main (String[] args) + { + MIMEMessage mmsg; + MIMEMessagePart mmsgpart; + FileOutputStream fos; + TestExtBodyMessagePart tm = new TestExtBodyMessagePart (); + + try + { + System.out.println ("Creating Message w/ External Body"); + + // Create a message part + System.out.println ("Creating MessagePart"); + mmsgpart = new MIMEMessagePart (); + + // set the attributes of the messagePart + mmsgpart.setContentSubType ("external-body"); + mmsgpart.setContentTypeParams ("access-type=local-file;name=\"e:\\share\\jpeg1.jpg\""); + mmsgpart.setContentEncoding (MIMEBodyPart.E7BIT); + + // set ext-part headers + mmsgpart.setExtBodyHeader ("Content-Type", "image/jpg"); + + // Create a new Message with this messagePart. + System.out.println ("Creating a new Message "); + MIMEMessage msg = new MIMEMessage (); + + System.out.println ("Setting the above MessagePart as body of this Message."); + msg.setBody (mmsgpart, false); + + System.out.println ("Setting headers in the new Message"); + msg.setHeader ("Subject", "Test Message w/ external MessagePart Body"); + + System.out.println ("Setting other headers in the new Message"); + setOtherHeaders (msg); + + fos = new FileOutputStream (tmpdir + "MsgWithExtBodyMessagePart.out"); + System.out.println ("Writing message to output Stream"); + msg.putByteStream (fos); + } + catch (Exception e) + { + System.out.println ("Caught exception: " + e); + } + + } // main() + +} // TestExtBodyMessagePart diff --git a/msgsdk/java/protocol/MIME/examples/multi_part/ReadMe.txt b/msgsdk/java/protocol/MIME/examples/multi_part/ReadMe.txt new file mode 100644 index 00000000000..6fee38aa30c --- /dev/null +++ b/msgsdk/java/protocol/MIME/examples/multi_part/ReadMe.txt @@ -0,0 +1,13 @@ +This directory contains an example program that demonstrates the use of the Netscape +Messaging MIME Encoder java API. + +To compile and run: +------------------- + +(1) Make sure CLASSPATH includes the msg-sdk jar file (proapi.jar). + When installed proapi.jar is located at /packages/proapi.jar. + Make sure CLASSPATH also includes the directory you are compiling in. + +(2) Compile: javac *.java +(3) Run: java + diff --git a/msgsdk/java/protocol/MIME/examples/multi_part/TestMultiPartMessage.java b/msgsdk/java/protocol/MIME/examples/multi_part/TestMultiPartMessage.java new file mode 100644 index 00000000000..5f5efa55503 --- /dev/null +++ b/msgsdk/java/protocol/MIME/examples/multi_part/TestMultiPartMessage.java @@ -0,0 +1,249 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.*; + + +/** + * Example program for MIME Encoder API. + * Demonstrates how to create messages with multipart content from scratch. + * Please note that the classes MIMEMessage and MIMEMultiPart contain + * convenience methods to create a multi-part message with two parts, in a + * simple and single step. Please see other MIME examples provided for that + * purpose. This example demonstrates how to build a multi-part message from + * ground up. + */ +public class TestMultiPartMessage +{ + private static String tmpdir; + + public TestMultiPartMessage () + { + String sep = System.getProperty ("path.separator"); + + if (sep.equals (";")) + tmpdir = "C:\\temp\\"; + else + tmpdir = "/tmp/"; + } + + public static void setOtherHeaders (MIMEMessage mmsg) + { + try + { + mmsg.setHeader ("From", "Prasad Yendluri "); + mmsg.setHeader ("To", "Prasad Yendluri "); + mmsg.setHeader ("CC", "Prasad Yendluri "); + mmsg.setHeader ("Reply-To", "Prasad Yendluri "); + mmsg.setHeader ("X-MyTest-Header", "Test-Header-1"); + } + catch (Exception e) + { + System.out.println ("setOtherHeader> Caught exception: " + e); + } + } + + // create a message part w/ external-body + public MIMEMessagePart createMsgPartExt () + { + MIMEMessagePart l_msgpart; + + try + { + // Create a message part + l_msgpart = new MIMEMessagePart (); + + // set the attributes of the messagePart + l_msgpart.setContentSubType ("external-body"); + l_msgpart.setContentTypeParams ("access-type=local-file;name=\"e:\\share\\jpeg1.jpg\""); + l_msgpart.setContentEncoding (MIMEBodyPart.E7BIT); + + // set ext-part headers + l_msgpart.setExtBodyHeader ("Content-Type", "image/jpg"); + + return l_msgpart; + + } + catch (Exception e) + { + System.out.println ("createMsgPartExt Caught exception: " + e); + return null; + } + } + + + // create a text-part + public MIMEBasicPart createTextPart (String inText) + { + MIMEBasicPart l_mbp; + + try + { + l_mbp = new MIMEBasicPart (MIMEBasicPart.TEXT); + l_mbp.setContentSubType ("plain"); + l_mbp.setContentTypeParams ("charset=us-ascii"); + l_mbp.setContentID("Text_BasicPart_ContentID"); + //l_mbp.setContentEncoding(MIMEBodyPart.E7BIT); + l_mbp.setContentDisposition(MIMEBodyPart.INLINE); + //l_mbp.setContentDisposition(MIMEBodyPart.ATTACHMENT); // if you like + l_mbp.setContentDispParams("TextPart Disp-Params"); + l_mbp.setContentDispParams("Some_Description"); + l_mbp.setHeader ("X-TextPartHeader-1", "myValue-1"); + l_mbp.setHeader ("X-TextPartHeader-2", "myValue-2"); + l_mbp.setBodyData (inText); + + return l_mbp; + } + catch (Exception e) + { + System.out.println ("createTextPart Caught exception: " + e); + return null; + } + } + + + // create a basic-part from the filename passed + public MIMEBasicPart createFilePart (String filename, int encoding) + { + MIMEBasicPart l_filePart; + FileInputStream l_fis; + fileMIMEType l_fmt; + + try + { + // get the mime-type info on the file passed + l_fmt = MIMEHelper.getFileMIMEType (filename); + if (l_fmt == null) + return null; + + // create a new basic-part for the file and set the attributes + l_filePart = new MIMEBasicPart (l_fmt.content_type); + l_filePart.setContentSubType (l_fmt.content_subtype); + if (l_fmt.content_params != null) + l_filePart.setContentTypeParams (l_fmt.content_params); + else + l_filePart.setContentTypeParams (new String ("name=" + l_fmt.file_shortname)); + + if (encoding == -1) + l_filePart.setContentEncoding(l_fmt.mime_encoding); + else + l_filePart.setContentEncoding (encoding); // need sanity check on encoding + + l_filePart.setContentDispParams (new String ("filename=" + l_fmt.file_shortname)); + l_filePart.setContentDescription (l_fmt.file_shortname); + + // open input-stream to the file + l_fis = new FileInputStream (filename); + + // set the stream as body-data + l_filePart.setBodyData (l_fis); + + // all done. Return the part + return l_filePart; + } + catch (Exception e) + { + System.out.println ("createTextPart Caught exception: " + e); + return null; + } + } + + public static void main (String[] args) + { + MIMEMessage mmsg; + MIMEMultiPart mmp; + MIMEBasicPart bpText, bpFile; + MIMEMessagePart mmsgpart; + FileOutputStream fos; + TestMultiPartMessage tm = new TestMultiPartMessage (); + String filename = null; + int encoding = -1; + + if (args.length < 1) + { + System.out.println("usage: java TestMultiPartMessage "); + System.exit(0); + } + + filename = args[0]; + + if (args.length > 1) + { + if (args[1].equalsIgnoreCase("B")) + encoding = MIMEBodyPart.BASE64; + + if (args[1].equalsIgnoreCase("Q")) + encoding = MIMEBodyPart.QP; + } + + try + { + // Create the MultiPart w/ mixed subtype + mmp = new MIMEMultiPart (); + mmp.setContentSubType ("mixed"); // You could use other subtypes (e.g."alternate") here + mmp.setContentTypeParams ("test-params"); + + // create a text-part (basic-part) and add it to the multi-part + bpText = tm.createTextPart ("Hello this a simple text part."); + if (bpText != null) + mmp.addBodyPart (bpText, false); // flase => don't clone it + + // create a basic-part from the file-name passed and add it to the multi-part + bpFile = tm.createFilePart (filename, encoding); + + if (bpFile != null) + { + mmp.addBodyPart (bpFile, false); // flase => don't clone it + } + + // Create a message part (external-body) + mmsgpart = tm.createMsgPartExt (); + + if (bpFile != null) + mmp.addBodyPart (mmsgpart, false); // flase => don't clone it + + // o.k we now have a multi-part w/ three parts + // make a MIMEMessage out of it. + mmsg = new MIMEMessage (); + mmsg.setBody (mmp, false); + + // Set needed headers of the new Message + mmsg.setHeader ("Subject", "Test Message w/ MultiPart content"); + + // Set other headers of the Message + setOtherHeaders (mmsg); + + // Now the message is completely built! + // Encode it in MIME canonical form + fos = new FileOutputStream (tmpdir + "MsgWithMultiPart.out"); + mmsg.putByteStream (fos); + + } + catch (Exception e) + { + System.out.println ("Caught exception: " + e); + } + + } // main() + +} // TestMultiPartMessage diff --git a/msgsdk/java/protocol/MIME/src/ByteBuffer.java b/msgsdk/java/protocol/MIME/src/ByteBuffer.java new file mode 100644 index 00000000000..e0f0ea5a321 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/ByteBuffer.java @@ -0,0 +1,312 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; + +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * Mutable subclass of ByteString. + */ +public final class ByteBuffer extends ByteString implements Cloneable { + boolean wasCloned; + + /** + * Construct a ByteBuffer of the given capacity + */ + public ByteBuffer (int capacity) { + super(); + buffer = new byte[capacity]; + wasCloned = false; + } + + /** + * Construct a ByteBuffer of the given capacity + */ + public ByteBuffer (ByteString bs) { + super(); + buffer = bs.buffer; + hi = bs.hi; + lo = bs.lo; + wasCloned = false; + } + + /** + * Construct a ByteBuffer of the given capacity + * no clone version + */ + public ByteBuffer (byte[] b) + { + super(); + buffer = b; + hi = b.length; + lo = 0; + wasCloned = false; + } + + /** + * Construct a ByteBuffer of the given capacity + * no clone version + */ + public ByteBuffer (byte[] b, int len) + { + super(); + buffer = b; + hi = len; + lo = 0; + wasCloned = false; + } + + /**/ + + /** + * Ensure there is capacity for appending num bytes. + */ + private final void ensureCapacity (int num) { + byte[] newBuffer = null; + int newsize; + + if (hi == lo) erase(); + if (buffer.length - hi >= num) return; + + newsize = num + (hi-lo); + if (newsize*2 < buffer.length) { + newsize = buffer.length; + if (!wasCloned) newBuffer = buffer; + } + else if (newsize < buffer.length*2) newsize = buffer.length*2; + + if (newBuffer == null) + newBuffer = new byte[newsize]; + + System.arraycopy(buffer, lo, newBuffer, 0, hi-lo ); + + buffer = newBuffer; + wasCloned = false; + hi -= lo; + lo = 0; + } + + /** + * Append a ByteString. Not MT-safe. + */ + public final void append (ByteString that) { + ensureCapacity(that.size()); + System.arraycopy(that.buffer, that.lo, this.buffer, this.hi, that.size()); + this.hi += that.size(); + } + + /** + * Append a substring of a ByteString. Not MT-safe. + */ + public final void append (ByteString that, int start) { + int substrSize = that.size() - start; + + if (substrSize < 0) throw new IndexOutOfBoundsException(); + ensureCapacity(substrSize); + System.arraycopy(that.buffer, that.lo+start, this.buffer, this.hi, + substrSize); + this.hi += substrSize; + } + + /** + * Append a substring of a ByteString. Not MT-safe. + */ + public final void append (ByteString that, int start, int finish) { + if (start > finish || finish > that.size()) + throw new IndexOutOfBoundsException(); + ensureCapacity(finish - start); + System.arraycopy(that.buffer, that.lo+start, this.buffer, this.hi, finish-start); + this.hi += (finish-start); + } + + /** + * Append a byte[]. Not MT-safe. + */ + public final void append (byte[] that) { + ensureCapacity(that.length); + System.arraycopy(that, 0, this.buffer, this.hi, that.length); + this.hi += that.length; + } + + /** + * Append a byte[]. Not MT-safe. + */ + public final void append (byte[] that, int len ) + { + ensureCapacity(len); + System.arraycopy(that, 0, this.buffer, this.hi, len); + this.hi += len; + } + + /** + * Add a long + */ + public final void append (long n) { + ensureCapacity(8); + for (int i=hi+8; --i>=hi; ) { + buffer[i] = (byte)(n & 0xff); + n >>= 8; + } + hi += 8; + } + + /** + * Add an int + */ + public final void append (int n) { + ensureCapacity(4); + for (int i=hi+4; --i>=hi; ) { + buffer[i] = (byte)(n & 0xff); + n >>= 8; + } + hi += 4; + } + + /** + * Add a byte + */ + public final void append (byte n) { + ensureCapacity(1); + buffer[hi] = n; + hi++; + } + + /** + * Erase the contents of the ByteBuffering. Not MT safe. + * Will not shrink capacity. + */ + public final void erase () { + if (!wasCloned) hi = 0; + lo = hi; + } + + /** + * Defragment the contents of buffer. Not MT safe. + */ + public final void cleanup () { + if (wasCloned || lo+lo <= buffer.length) + return; + System.arraycopy(buffer, lo, buffer, 0, hi-lo); + hi -= lo; + lo = 0; + } + + /** + * Return substring from offset start (inclusive) till offset + * finish (exclusive). + */ + public ByteString substring (int start, int finish) { + ByteString result = super.substring(start, finish); + wasCloned = true; + return result; + } + + /** + * Return substring to the right of the given offset (inclusive). + */ + public ByteString substring (int start) { + ByteString result = super.substring(start); + wasCloned = true; + return result; + } + + /** + * Add a line. Not MT-safe. + */ + public final void addLine (ByteString line) { + append(line); + append(CRLF); + } + + /** + * Add a line. Not MT-safe. + */ + public final void addLine (byte[] line) { + append(line); + append(CRLF); + } + + /** + * Read a line read (and maybe more) from a stream. Not MT-safe. + * Blocks until one line is read. May read multiple lines. + */ + public final void readLine (InputStream stream) + throws IOException { + while (noLines()) { + bulkRead(stream); + } + } + + /** + * Bulk read from a stream. Not MT-safe. + * Returns the number of bytes read. + */ + public final int bulkRead (InputStream stream) + throws IOException { + + int n = -1; + ensureCapacity(1024); // TODO: is this a good number? + for (int attempt = 0; attempt < 10; attempt++) { + n = stream.read(buffer, hi, buffer.length-hi); + if (n > 0) { + hi += n; + return n; + } + else if (n == 0) { + System.err.println("BufferedInputStream.read(byte[], int, int) returns 0 on attempt #" + attempt); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + System.err.println(e + " while waiting in bulkRead"); + } + } + else { + throw new IOException("Bulk read returns " + n + " bytes"); + } + } + throw new IOException("Bulk read returns " + n + " bytes"); + } + + /** + * Clones an instance of the ByteBuffer object. + * @exception CloneNotSupportedException could be thrown by constituent components. + */ + public Object clone () throws CloneNotSupportedException + { + ByteBuffer l_theClone = (ByteBuffer) super.clone(); + + return (l_theClone); + } + + public InputStream getInputStream() + { + return new ByteArrayInputStream( buffer, lo, hi ); + } + + public void setSize( int len ) { ensureCapacity( len ); } +} diff --git a/msgsdk/java/protocol/MIME/src/ByteString.java b/msgsdk/java/protocol/MIME/src/ByteString.java new file mode 100644 index 00000000000..8c1b71ebf8b --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/ByteString.java @@ -0,0 +1,501 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * ByteString is similar to java.lang.String, except that it contains + * bytes instead of characters. + */ +public class ByteString extends Object implements Cloneable { + protected byte[] buffer; + protected int lo; + protected int hi; + + protected static byte CR = '\r'; + protected static byte LF = '\n'; + protected static byte[] CRLF = "\r\n".getBytes(); + + protected ByteString () { + buffer = null; + lo = 0; + hi = 0; + } + + public ByteString (String string) { + buffer = string.getBytes(); + lo = 0; + hi = buffer.length; + } + + public ByteString (byte[] buffer) { + this.buffer = buffer; + this.lo = 0; + this.hi = buffer.length; + } + + public ByteString (byte[] buffer, int lo, int hi) { + if (lo > hi) throw new IndexOutOfBoundsException(); + this.buffer = buffer; + this.lo = lo; + this.hi = hi; + } + + /** + * hashCode() -- Overrides java.lang.Object.hashCode() + */ + public final int hashCode () { + int result = 0; + // TODO: improve hash function + for (int i = lo; i < hi; i++) { + result += buffer[i]; + } + return result; + } + + /** + * equals() -- Overrides java.lang.Object.equals() + */ + public final boolean equals (Object that) { + if (this == that) + return true; + else + return equals(((ByteString) that).buffer, ((ByteString) that).lo, ((ByteString) that).size()); + } + + public final boolean equals (byte[] buf) { + return equals(buf, 0, buf.length); + } + + public final boolean equals (byte[] buf, int low, int length) { + + if (this.size() != length) + return false; + for (int i = 0; i < length; i++) { + if (this.byteAt(i) != buf[low++]) + return false; + } + return true; + } + + + /** + * Compare this to that in lex order + */ + public final boolean greaterOrEqual (ByteString that) { + int m = this.size(); + int n = that.size(); + for (int i = 0; i < n; i++) { + if (i >= m) + return false; + else { + byte x = this.byteAt(i); + byte y = that.byteAt(i); + if (x < y) + return false; + else if (x > y) + return true; + } + } + return true; + } + + /** + * Return number of bytes + */ + public final int size () { + return (hi-lo); + } + + /** + * Return byte at given offset. + */ + public final byte byteAt (int i) { + if (lo + i >= hi) throw new IndexOutOfBoundsException(); + return buffer[lo+i]; + } + + /** + * Check whether the ByteString starts with the given byte[]. + * Ignores case. + */ + private final boolean startsWith (byte[] that) { + if (that.length > hi - lo) return false; + for (int i = 0; i < that.length; i++) { + char x = (char)(buffer[lo+i]); + char y = (char)(that[i]); + if (x != y && (x < 'a' || x > 'z' || (x + 'A' - 'a') != y)) + return false; + } + return true; + } + + /** + * Returns the offset for the first occurrence of a given byte + * in the ByteString if present, and -1 otherwise. + */ + public final int firstIndexOf (byte x) { + for (int i = lo; i < hi; i++) { + if (buffer[i] == x) + return (i-lo); + } + return (-1); + } + + /** + * Returns the offset for the first occurrence of a given byte + * in the ByteString to the right of the given offset if present, and + * -1 otherwise. + */ + public final int firstIndexOf (byte x, int offset) { + for (int i = lo+offset+1; i < hi; i++) { + if (buffer[i] == x) + return (i-lo); + } + return (-1); + } + + /** + * Returns the offset for the last occurrence of a given byte + * in the ByteString if present, and -1 otherwise. + */ + public final int lastIndexOf (byte x) { + for (int i = hi; --i >= lo; ) { + if (buffer[i] == x) + return (i-lo); + } + return -1; + } + + /** + * Returns the offset for the last occurrence of a given byte + * in the ByteString to the left of the given offset if present, and + * -1 otherwise. + */ + public final int lastIndexOf (byte x, int offset) { + for (int i = lo+offset; --i >= lo; ) { + if (buffer[i] == x) + return (i-lo); + } + return -1; + } + + /** + * Return the offset for the first occurrence of a given byte[] + * in the ByteString to the right of the given offset if present, + * and -1 otherwise. Case insensitive. + */ + public final int firstIndexOf (byte[] that) { + return firstIndexOf(that, 0); + } + + /** + * Return the offset for the first occurrence of a given byte[] + * in the ByteString to the right of the given offset if present, + * and -1 otherwise. Case insensitive. + */ + public final int firstIndexOf (byte[] that, int offset) { + + for (int i = lo+offset; i <= hi-that.length; i++) { + int j; + for (j = 0; j < that.length; j++) { + char x = (char)(buffer[i+j]); + char y = (char)(that[j]); + if (x != y && (x < 'a' || x > 'z' || (x + 'A' - 'a') != y)) + break; + } + if (j >= that.length) { + return i-lo; + } + } + return -1; + } + + /** + * Convert to String + */ + public final String toString () { + return new String(buffer, lo, hi-lo); + } + + /** + * Convert to byte[] + * clone version + */ + public final byte[] getBytes () { return getBytes( true ); } + + /** + * Convert to byte[] + * clonable version + */ + public final byte[] getBytes ( boolean clone ) + { + if ( clone ) + { + byte[] result = new byte[hi-lo]; + System.arraycopy(buffer, lo, result, 0, hi-lo); + return result; + } + + return buffer; + } + + public final byte[] getBytes ( int len[] ) + { + len[0] = hi; + return buffer; + } + + /** + * Extract X out of ByteString X+Y+Z, where Y is the given delimiter. + * Replace original ByteString (X+Y+Z) by Z. Not MT-safe. + * Returns null if Y is absent. + */ + public final ByteString extractTill (byte[] y) { + int offset = firstIndexOf(y, 0); + if (offset < 0) { + return null; + } + else { + ByteString result = substring(0, offset); + lo += (offset + y.length); + return result; + } + } + + + /** + * Remove Y from ByteString Y+Z, where Y is the given token, + * If successful, replace original ByteString (Y+Z) by Z, and + * & return true. Else return false. Not MT-safe. + * Throw ArrayIndexOutOfBoundsException if ByteString does + * not start with Y. + */ + public final boolean extractToken (byte[] y) { + if (startsWith(y)) { + lo += y.length; + return true; + } + else { + return false; + } + } + + /** + * Discard COUNT bytes from ByteString + */ + public final void discard(int count) { + if (lo + count > hi) throw new IndexOutOfBoundsException(); + lo += count; + } + + /** + * Discard X+Y from ByteString X+Y+Z, where Y is the given token. + * Throw ArrayIndexOutOfBoundsException if Y is absent. + */ + public final void discardTill (byte[] y) { + lo += (firstIndexOf(y, 0) + y.length); + } + + /** + * Discard X+Y from ByteString X+Y+Z, where Y is LF or CRLF. + * Throw ArrayIndexOutOfBoundsException if Y is absent. + */ + public final void discardLine () { + lo += (firstIndexOf(LF, 0) + 1); + } + + /** + * Checks for absence of complete lines + */ + public final boolean noLines () { + return firstIndexOf(LF, 0) < 0; + } + + /** + * Checks for absence of complete lines to the right of the given offset + */ + public final boolean noLines (int offset) { + return firstIndexOf(LF, offset) < 0; + } + + /* + * Extract the first line and return it as a substring. + * Throw ArrayIndexOutOfBoundsException if there are no lines. + */ + public final ByteString extractLine() { + ByteString result; + int offset = firstIndexOf(LF, 0); + if (offset > 1 && byteAt(offset-1) == CR) { + result = substring(0, offset-1); + lo += (offset + 1); + } + else { + result = substring(0, offset); + lo += (offset + 1); + } + + return result; + } + + + /** + * Extract the first line and write it to given output stream. + * Throw ArrayIndexOutOfBoundsException if there are no lines. + * return number of byte wrote + */ + + public final int extractLine(OutputStream stream) + throws IOException { + + int origin = lo; + + int offset = firstIndexOf(LF, 0); + if (offset > 1 && byteAt(offset-1) == CR) { + stream.write(buffer, lo, offset-1); + stream.write(CRLF); + lo += (offset + 1); + } + else { + stream.write(buffer, lo, offset); + stream.write(CRLF); + lo += (offset + 1); + } + return (lo - origin); + } + + public void discardLeadingSpace() + { + // skip leading space + for (; lo < hi; lo++) { + if (buffer[lo] != ' ' ) + break; + } + } + + /** + * bulk write to a stream. + */ + public final void write (OutputStream stream) + throws IOException { + + stream.write(buffer, lo, hi-lo); + } + + /** + * Extract a long + **/ + public final long extractLong() { + long n = 0; + for (int i = lo; i < lo+8; i++) { + n <<= 8; + n += buffer[i]; + } + lo += 8; + return n; + } + + /** + * Extract an int + **/ + public final int extractInt() { + int n = 0; + for (int i = lo; i < lo+4; i++) { + n <<= 8; + n += buffer[i]; + } + lo += 4; + return n; + } + + /** + * Return substring from offset start (inclusive) till offset + * finish (exclusive). + */ + public ByteString substring (int start, int finish) { + if (start > finish || lo+finish > hi) + throw new IndexOutOfBoundsException(); + return new ByteString(buffer, lo+start, lo+finish); + } + + /** + * Return substring from offset start (inclusive) till the + * end of the ByteString. + */ + public ByteString substring (int start) { + if (lo+start > hi) throw new IndexOutOfBoundsException(); + return new ByteString(buffer, lo+start, hi); + } + + /** + * Display the contents of the ByteString for + * the purpose of debugging + */ + public void displayBuffer (String caption, BufferedOutputStream stream) { + int saved = lo; + + try { + for (int i = 0; !noLines(); i++) { + stream.write(caption.getBytes()); + extractLine(stream); + } + stream.write(caption.getBytes()); + stream.write(getBytes()); + lo = saved; + stream.flush(); + } catch (IOException e) { + System.err.println(e + " while displaying contents of ByteString"); + } + } + + + /** + * Clones an instance of the ByteString object. + * @exception CloneNotSupportedException could be thrown by constituent components. + */ + public Object clone () throws CloneNotSupportedException + { + ByteString l_theClone = (ByteString) super.clone(); + + if (buffer != null) + { + l_theClone.buffer = new byte [buffer.length]; + System.arraycopy (buffer, 0, l_theClone.buffer, 0, buffer.length); + } + + return (l_theClone); + } + + + public void setSize( int len ) + { + hi = len; + lo = 0; + } +} diff --git a/msgsdk/java/protocol/MIME/src/Header.java b/msgsdk/java/protocol/MIME/src/Header.java new file mode 100644 index 00000000000..1372e9083f0 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/Header.java @@ -0,0 +1,215 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; +import java.lang.*; +import java.util.*; + + +/** + * The Header class is used to represent the Header of the message. + * @author Prasad Yendluri + */ +public class Header implements Cloneable +{ + protected String m_headerName; + private String m_headerValue; + + /** + * Header constructor. + * Creates a Header object given header name and value strings. + * @param name Name of the header. + * @param value Value of the header. + * @exception MIMEException If either name or value is null. + */ + public Header (String name, String value) throws MIMEException + { + if (name == null || value == null) + throw new MIMEException ("Inavlid null (or) ."); + + m_headerName = name; + m_headerValue = value; + } + + + /** + * Header constructor. + * Creates a Header object given a string in : format. + * Any '\r' and '\n' chars at the end of the line are stripped. + * All the chars in inStr must map to ASCII. + * @param inStr. A String with header in the form : + * @exception MIMEException If inStr is null or not in : form. + */ + public Header (String inStr) throws MIMEException + { + int sepIndex = 0, strLen; + + if (inStr == null) + throw new MIMEException ("String in : form expected."); + + inStr.trim(); + + strLen = inStr.length(); + + while (strLen > 0) + { + if ((inStr.lastIndexOf ('\n', strLen-1) > 0) || + (inStr.lastIndexOf ('\r', strLen-1) > 0)) + strLen--; + else + break; + + } + + if (strLen <= 3) + throw new MIMEException ("String in : form expected."); + + sepIndex = inStr.indexOf (':'); + + if ((sepIndex == 0) || (sepIndex == strLen-1)) + throw new MIMEException ("String in : form expected."); + + m_headerName = inStr.substring (0, sepIndex); + m_headerValue = inStr.substring (sepIndex+1, strLen); + } + + /** + * Header constructor. + * Creates a Header object given a byte array with header in the form :. + * Any '\r' and '\n' chars at the end of the line are stripped. + * All the chars in line[] must be ASCII. + * @param line. A byte array with header in the form : + * @exception MIMEException If line is null or not in : form. + */ + public Header (byte[] line) throws MIMEException + { + int linelen, sepIndex = 0; + + if (line == null) + throw new MIMEException ("byte[] in : form expected."); + + linelen = line.length; + + // Trim trailing spaces and CR LFs. + while ((linelen > 0) && ((line [linelen-1] == '\n') || + (line [linelen-1] == '\r') || (line [linelen-1] == ' '))) + linelen--; + + // NOTE: No need to check for leading spaces as we trim Name and Value anyway. + + if (linelen <= 3) + throw new MIMEException ("byte[] in : form expected."); + + for (int i = 0; i < linelen; i++) + { + if (line [i] == ':') + { + sepIndex = i; + break; + } + } + + if ((sepIndex == 0) || (sepIndex == linelen-1)) + throw new MIMEException ("byte[] in : form expected."); + + m_headerName = new String (line, 0, sepIndex); + m_headerValue = new String (line, sepIndex+1, (linelen - (sepIndex +1))); + } + + + /** + * Returns the name of this header. + */ + public String getName () + { + return (m_headerName.trim()); + } + + /** + * Returns the value of this header. + */ + public String getValue () + { + return (m_headerValue.trim()); + } + + /** + * Sets the name of this header. + * @param name Name of the header to set. + */ + protected void setName ( String name ) throws MIMEException + { + if (name == null) + throw new MIMEException ("Inavlid null (or) ."); + + m_headerName = name; + } + + /** + * Sets the value of this header. + * @param value Value of the header to set. + */ + protected void setValue ( String value ) throws MIMEException + { + if (value == null) + throw new MIMEException ("Inavlid null (or) ."); + + m_headerValue = value; + } + + /** + * Returns the compelete header-line in the form : with CRLF termination. + */ + public String getLine () + { + String line; + line = new String (m_headerName.trim() + ": " + m_headerValue.trim() + "\r\n"); + return (line); + } + + /** + * Clones an instance of the Header object. + */ + public Object clone () + { + Header l_theClone; + + try + { + l_theClone = (Header) super.clone(); + } + catch (CloneNotSupportedException e) + { + return (null); + } + + if (m_headerName != null) + l_theClone.m_headerName = m_headerName; + if (m_headerValue != null) + l_theClone.m_headerValue = m_headerValue; + + return (l_theClone); + } +} diff --git a/msgsdk/java/protocol/MIME/src/MIMEBasicPart.java b/msgsdk/java/protocol/MIME/src/MIMEBasicPart.java new file mode 100644 index 00000000000..609a3b51011 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEBasicPart.java @@ -0,0 +1,1050 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + + +package netscape.messaging.mime; +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.MIMEParser; + +/** + * The MIMEBasicPart class is a common class used for all Basic + * MIME BodyPart types: + * Text, Image, Audio, Video, and Application. It does not represent structured + * parts such as MIMEMessagePart and MIMEMultiPart. + * @author Prasad Yendluri + */ +public class MIMEBasicPart extends MIMEBodyPart implements Cloneable +{ + + /** + * Content (primary) type Text. + */ + public static final int TEXT = 0; + /** + * Content (primary) type Audio. + */ + public static final int AUDIO = 1; + /** + * Content (primary) type Image. + */ + public static final int IMAGE = 2; + /** + * Content (primary) type Video. + */ + public static final int VIDEO = 3; + /** + * Content (primary) type Application. + */ + public static final int APPLICATION = 4; + + + +//=========================================================================== +// +// Internal members not visible at the API +// +//=========================================================================== + + // Encodings + private static final int BASE64 = MIMEBodyPart.BASE64; + private static final int QP = MIMEBodyPart.QP; + private static final int BINARY = MIMEBodyPart.BINARY; + private static final int E7BIT = MIMEBodyPart.E7BIT; + private static final int E8BIT = MIMEBodyPart.E8BIT; + private static final int EIGNORE = 99; + public static final int UNINITIALIZED = -1; + private static final int MAXDATABUFSIZE = 1111111; + + private static final String[] m_stringContentTypes = { "Text", "Audio", "Image", + "Video", "Application" }; + private static final String[] m_stringEncoding = { "base64", "quoted-printable", + "binary", "7bit", "8bit" }; + private static final String[] m_stringDisposition = { "Attachment", "Inline"}; + + private int m_contentType; + private String m_contentMD5; + private int m_contentTransferEncoding; + private int m_parsedPart; + + // The key to the hashTable below is the name of the header field. + // To make sure we handle case differences in the header-name, we + // must convert the header name to lower-case always prior to the + // hash look-up. The entries added to the hash table are objects of + // class Header. + private Hashtable m_mimeHeadersTable; + + // Buffer to hold body-data (not encoded). We always store un-encoded. + // Buffer stores data as parsed from input message or as given by the user + // when constructing this bodyPart. + private ByteBuffer m_databuf; + private byte m_readbuf[]; + private InputStream m_dataStream; + + private int m_nStartMessageDataIndex; + private int m_nEndMessageDataIndex; + private int m_nMessageDataLen; + private boolean m_bDecodedData; + protected boolean m_endData; + protected MIMEMultiPart m_parentContainer; + + +//=========================================================================== +// +// CONSTRUCTORS +// +//=========================================================================== + + /** + * Constructs a MIMEBasicPart object with the given content-type. + * @param contentType Content type. Values: + * TEXT, AUDIO, IMAGE, VIDEO, APPLICATION. + * @see MIMEBasicPart#TEXT + * @see MIMEBasicPart#AUDIO + * @see MIMEBasicPart#IMAGE + * @see MIMEBasicPart#VIDEO + * @see MIMEBasicPart#APPLICATION + * @exception MIMEException If ContentType passed is invalid. + */ + public MIMEBasicPart (int contentType) throws MIMEException + { + switch (contentType) + { + case MIMEBasicPart.TEXT: + m_contentType = TEXT; + break; + case MIMEBasicPart.AUDIO: + m_contentType = AUDIO; + break; + case MIMEBasicPart.IMAGE: + m_contentType = IMAGE; + break; + case MIMEBasicPart.VIDEO: + m_contentType = VIDEO; + break; + case MIMEBasicPart.APPLICATION: + m_contentType = APPLICATION; + break; + default: + throw new MIMEException ("Invalid content-type: " + contentType); + } + + m_parsedPart = 0; + m_contentDisposition = UNINITIALIZED; + m_contentTransferEncoding = UNINITIALIZED; + m_mimeHeadersTable = new Hashtable(); + m_databuf = null; + m_readbuf = null; + m_dataStream = null; + + m_nStartMessageDataIndex = UNINITIALIZED; + m_nEndMessageDataIndex = UNINITIALIZED; + m_nMessageDataLen = 0; + m_UserObject = null; + m_bDecodedData = false; + } + + + /** + * Default constructor. + * Constructs a MIMEBasicPart object with MIMEBasicPart#TEXT content-type. + */ + public MIMEBasicPart() throws MIMEException + { + this (MIMEBasicPart.TEXT); + } + + +//=========================================================================== +// +// Methods overriden from MIMEBodyPart +// +//=========================================================================== + + /** + * Returns Content-Type of this MIME Part. + * @see MIMEBodyPart#getContentType + */ + public String getContentType () + { + String ct = new String (m_stringContentTypes [m_contentType]); + return (ct); + } + + +//=========================================================================== +// +// Methods Specific to this Class alone. +// +//=========================================================================== + + /** + * Returns Content-MD5 of this MIME Part or NULL if Content-MD5 is not present. + * @see #setContentMD5 + */ + public String getContentMD5 () + { + if (m_contentMD5 == null) + return (null); + + return (m_contentMD5); + } + + /** + * Sets Content-MD5 of this MIME Part. + * @param cid String to use as Content-ID. + * @see #getContentMD5 + */ + public void setContentMD5 (String md5) + { + m_contentMD5 = md5; + } + + /** + * Returns an InputStream to this Part's Data after decoding any + * transfer encoding. + * @exception IOException If an IO error occurs. + * @exception MIMEException If the BodyData is not present. + * or an error is detected during decoding. + * @see #setBodyData + */ + public InputStream getBodyData () throws IOException, MIMEException + { + if (m_databuf == null && m_dataStream == null) + { + throw new MIMEException ("getBodyData(): No BodyData present." ); + } + + if (m_databuf != null) + { + ByteArrayInputStream ins = new ByteArrayInputStream (m_databuf.getBytes()); + return (ins); + } + + if (m_dataStream.markSupported()) + m_dataStream.reset(); + + return (m_dataStream); + } + + /** + * Returns an ByteBuffer object, return null if no data + * carsonl, used by parser + */ + protected ByteBuffer getDataBuf() { return m_databuf; } + + + + /** + * Sets BodyData of this MIME Part. + * @param is Stream to read input data from. This data must be un-encoded raw data. + * @exception MIMEException If already set (or) if is parameter is null. + * @exception IOException If an IO error occurs. + * @see #getDataBuf + */ + public void setBodyData (InputStream is) throws MIMEException, IOException + { + if (m_databuf != null && m_dataStream != null) + { + throw new MIMEException ("setBodyData(): BodyData already set." ); + } + + if (is == null) + { + throw new MIMEException ("setBodyData(): null inputStream"); + } + + setMessageDataLen( is.available() ); + + if (m_nMessageDataLen <= 0) + { + m_nMessageDataLen = 0; + throw new MIMEException ("setBodyData(): no data in inputStream"); + } + + if (m_nMessageDataLen > MAXDATABUFSIZE) + { + m_dataStream = is; + if (is.markSupported()) + is.mark (2*m_nMessageDataLen); + } + else + { + ByteBuffer l_inbuf = new ByteBuffer (DATABUFSZ); + int l_read = is.read (l_inbuf.buffer, 0, DATABUFSZ); + + if (l_read > 0) + { + m_databuf = new ByteBuffer(DATABUFSZ); // initial size + } + else + { + throw new MIMEException ("setBodyData(): InputStream Empty."); + } + + while (l_read > 0) + { + l_inbuf.hi = l_read; + m_databuf.append (l_inbuf); + l_read = is.read (l_inbuf.buffer, 0, DATABUFSZ); + } + } + } + + /** + * Sets BodyData of this MIME Part. + * @param s Source string for input data. Must be un-encoded raw data. + * @exception MIMEException If already set or if is parameter is null. + */ + public void setBodyData (String s) throws MIMEException + { + if (m_databuf != null && m_dataStream != null) + { + throw new MIMEException ("setBodyData(): BodyData already set." ); + } + + if ( s != null ) + { + m_databuf = new ByteBuffer( s.length() ); + m_databuf.append( s.getBytes() ); + setMessageDataLen( s.length() ); + } + } + + // non clone version + // carsonl. Not Exposed at API + protected void setBodyData( ByteBuffer s ) throws MIMEException + { + if (m_databuf != null && m_dataStream != null) + { + throw new MIMEException ("setBodyData(): BodyData already set." ); + } + + if ( s != null ) + m_databuf = s; + } + + /** + * Sets BodyData of this MIME Part. + * Saves a reference to the passed byte buffer. + * Does not copy the data. + * @param s un-encoded raw data. + * @exception MIMEException If already set or if s parameter is null. + */ + public void setBodyData( byte s[] ) throws MIMEException + { + if (m_databuf != null && m_dataStream != null) + { + throw new MIMEException ("setBodyData(): BodyData already set."); + } + + if ( s != null ) + { + m_databuf = new ByteBuffer( s ); + setMessageDataLen( s.length ); + } + } + + + /** + * Deletes BodyData of this MIME Part. + * If no BodyData is present, this method has no effect. + */ + public void deleteBodyData () + { + m_databuf = null; + m_dataStream = null; + m_nMessageDataLen = 0; + } + + /** + * Outputs a byte-stream for this part with its MIME part headers and encoded + * body data. + * @param fullfilename Filename including full path of where to write the byte-stream. + * @exception IOException If an IO error occurs. + * @exception MIMEException If any required fields in the bodyPart are not set-up. + */ + public void putByteStream (String fullfilename) throws IOException, MIMEException + { + FileOutputStream fos; + + fos = new FileOutputStream (fullfilename); + + putByteStream (fos); + } + + /** + * Outputs a byte-stream for this part with its MIME part headers and encoded + * body data. + * @param os OutputStream to write to. + * @exception IOException If an IO error occurs. + * @exception MIMEException If any required fields in the bodyPart are not set-up. + */ + public void putByteStream (OutputStream os) throws IOException, MIMEException + { + if (m_databuf == null && m_dataStream == null) + { + throw new MIMEException ("MIMEBasicPart.putByteStream(). No bodyData!"); + } + + // Write out the headers first + StringBuffer l_hdrbuf = new StringBuffer (HDRBUFSZ); + byte[] l_bytebuf; + + // Headers in member variables + + // content-type + switch (m_contentType) + { + case TEXT: + l_hdrbuf.append ("Content-Type: text/"); + + if (m_contentSubType != null) + l_hdrbuf.append (m_contentSubType); + else + l_hdrbuf.append ("plain"); + + if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 ) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentTypeParams); + l_hdrbuf.append ("\r\n"); + } + else + { + // l_hdrbuf.append ("; "); + // l_hdrbuf.append ("charset=us-ascii\r\n"); + l_hdrbuf.append ("\r\n"); + } + break; + case AUDIO: + l_hdrbuf.append ("Content-Type: audio/"); + + if (m_contentSubType != null) + l_hdrbuf.append (m_contentSubType); + else + throw new MIMEException ("putByteStream: No content-subtype." ); + //l_hdrbuf.append ("basic"); + + if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 ) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentTypeParams); + l_hdrbuf.append ("\r\n"); + } + else + l_hdrbuf.append ("\r\n"); + break; + case IMAGE: + l_hdrbuf.append ("Content-Type: image/"); + + if (m_contentSubType != null) + l_hdrbuf.append (m_contentSubType); + else + throw new MIMEException ("putByteStream: No content-subtype."); + //l_hdrbuf.append ("jpeg"); + + if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 ) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentTypeParams); + l_hdrbuf.append ("\r\n"); + } + else + l_hdrbuf.append ("\r\n"); + break; + case VIDEO: + l_hdrbuf.append ("Content-Type: video/"); + if (m_contentSubType != null) + l_hdrbuf.append (m_contentSubType); + else + throw new MIMEException ("putByteStream: No content-subtype."); + //l_hdrbuf.append ("mpeg"); + + if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 ) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentTypeParams); + l_hdrbuf.append ("\r\n"); + } + else + l_hdrbuf.append ("\r\n"); + break; + case APPLICATION: + l_hdrbuf.append ("Content-Type: application/"); + if (m_contentSubType != null) + l_hdrbuf.append (m_contentSubType); + else + l_hdrbuf.append ("octet-stream"); + + if (m_contentTypeParams != null && m_contentTypeParams.length() > 0 ) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentTypeParams); + l_hdrbuf.append ("\r\n"); + } + else + l_hdrbuf.append ("\r\n"); + break; + default: + throw new MIMEException ("Invalid content-type: " + m_contentType); + } + + // write the header out to os + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Type: " + e.getMessage()); + } + + // contentID + if (m_contentID != null) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-ID: " + m_contentID); + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-ID: " + e.getMessage()); + } + } + + // contentMD5 + if (m_contentMD5 != null) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-MD5: " + m_contentMD5); + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-MD5: " + e.getMessage()); + } + } + + // contentDisposition + if (m_contentDisposition != UNINITIALIZED) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Disposition: " + + m_stringDisposition [m_contentDisposition]); + + if (m_contentDispParams != null) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentDispParams); + } + + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Disposition: " + e.getMessage()); + } + } + + // contentDescription + if (m_contentDescription != null) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Description: " + m_contentDescription); + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Description: " + e.getMessage()); + } + } + + // Content-transfer-encoding + // if (m_contentTransferEncoding == UNINITIALIZED) + // { + // if ((m_contentType == TEXT) && (m_contentTypeParams != null) && m_contentTypeParams.length() > 0) + // { + // String l_params = m_contentTypeParams.toLowerCase(); + // int idx = l_params.indexOf ("charset=us-ascii"); + // if (idx >= 0) + // m_contentTransferEncoding = E7BIT; + // else + // //m_contentTransferEncoding = BASE64; + // //m_contentTransferEncoding = E8BIT; + // m_contentTransferEncoding = EIGNORE; + // + // } + // else + // m_contentTransferEncoding = E7BIT; + // } + + if (m_contentTransferEncoding != EIGNORE && m_contentTransferEncoding != UNINITIALIZED) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Transfer-Encoding: " + + m_stringEncoding [m_contentTransferEncoding]); + l_hdrbuf.append ("\r\n"); + + try + { + // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Transfer-Encoding: " + e.getMessage()); + } + } + + // Do all the other headers + Header[] hdrs = getAllHeaders (); + if (hdrs != null) + for (int i = 0, len = hdrs.length; i < len; i++) + { + try + { + l_bytebuf = MIMEHelper.unicodeToASCII (hdrs[i].getLine()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException (hdrs[i].getLine() + "> " + e.getMessage()); + } + } + + // blank-line after headers + //l_bytebuf = new String("\r\n").getBytes("iso-8859-1"); + //os.write (LF); + os.write (CRLF); + + int encoding = m_contentTransferEncoding; + + if (encoding == UNINITIALIZED || encoding == EIGNORE) + encoding = E7BIT; + + // Write out the bodydata encoding if needed. + switch (encoding) // we know it is not UNINITIALIZED + { + case BASE64: + if (m_dataStream == null) + { + ByteArrayInputStream isb = + new ByteArrayInputStream (m_databuf.getBytes()); + MIMEHelper.encodeBase64 (isb, os); + } + else + { + if (m_dataStream.markSupported()) + m_dataStream.reset(); + MIMEHelper.encodeBase64 (m_dataStream, os); + } + break; + + case QP: + if (m_dataStream == null) + { + ByteArrayInputStream isq = + new ByteArrayInputStream (m_databuf.getBytes()); + MIMEHelper.encodeQP (isq, os); + } + else + { + if (m_dataStream.markSupported()) + m_dataStream.reset(); + MIMEHelper.encodeQP (m_dataStream, os); + } + break; + + case BINARY: + case E7BIT: + case E8BIT: + case EIGNORE: + if (m_dataStream == null) + { + os.write (m_databuf.getBytes()); + } + else + { + if (m_dataStream.markSupported()) + m_dataStream.reset(); + + if (m_readbuf == null) + m_readbuf = new byte [DATABUFSZ]; + + int l_read = m_dataStream.read (m_readbuf, 0, DATABUFSZ); + + while (l_read > 0) + { + os.write (m_readbuf, 0, l_read); + l_read = m_dataStream.read (m_readbuf, 0, DATABUFSZ); + } + } + break; + + default: + throw new MIMEException ("Invalid Content Transfer Encoding : " + + m_contentTransferEncoding); + } // switch + + //os.write (CRLF); + + } // putByteStream() + + /** + * Returns the size of this Part's BodyData. + * @return Size of BodyData or -1 if this part does not have bodyData. + */ + public int getSize () + { + if (m_databuf != null) + { + return (m_databuf.size()); + } + else if (m_dataStream != null) + { + return (m_nMessageDataLen); + } + + return (-1); + } + + /** + * Sets Content-Transfer-Encoding of this MIME Part. + * @param encoding Value that represents the encoding. + * @see MIMEBodyPart#BASE64 + * @see MIMEBodyPart#QP + * @see MIMEBodyPart#BINARY + * @see MIMEBodyPart#E7BIT + * @see MIMEBodyPart#E8BIT + * @exception MIMEException If encoding is invalid. + */ + public void setContentEncoding (int encoding) throws MIMEException + { + switch (encoding) + { + case BASE64: + case QP: + case BINARY: + case E7BIT: + case E8BIT: + m_contentTransferEncoding = encoding; + break; + default: + throw new MIMEException ("Invalid Content Transfer Encoding : " + encoding); + } + } + + /** + * Returns value of Content-Transfer-Encoding of this MIME Part. -1 if none present. + * @see MIMEBodyPart#BASE64 + * @see MIMEBodyPart#QP + * @see MIMEBodyPart#BINARY + * @see MIMEBodyPart#E7BIT + * @see MIMEBodyPart#E8BIT + */ + public int getContentEncoding () + { + if (m_contentTransferEncoding == UNINITIALIZED || m_contentTransferEncoding == EIGNORE) + return (-1); + + return (m_contentTransferEncoding); + } + + /** + * Sets a header of this body-part given name and value. + * To be used to set any header that is not explicitly supported with a + * set method, such as X-headers. Multiple invocations on the same header + * replace the old value. + * @param name Name of the header field. Should not include ':' + * @param value Value of the header field to be added. + * @exception MIMEException If either of name or value is NULL. + */ + public void setHeader (String name, String value) throws MIMEException + { + if ((name == null) || (value == null)) + throw new MIMEException ("Invalid NULL Header name or value"); + + if ( name.indexOf (':') > -1) + throw new MIMEException ("Invalid ':' in Header name"); + + // Check if the header is one of those they need to specify with other methods + String rslt = isOKtoset (name); + if (rslt != null) + throw new MIMEException ("Invalid setHeader(). Use method: " + rslt); + + String l_hashKey = name.toLowerCase(); + Header l_hdr = new Header (name, value); + m_mimeHeadersTable.put (l_hashKey, l_hdr); + } + + /** + * Appends the value to an existing header. Creates new header if one does + * not exist already. + * @param name Name of the header field. + * @param value Value of the header field to be added. + * @exception MIMEException If either name or value are NULL + */ + public void addHeader (String name, String value) throws MIMEException + { + if ((name == null) || (value == null)) + throw new MIMEException ("Invalid NULL Header name or value"); + + String l_hashKey = name.toLowerCase(); + + Header l_hdr = (Header) m_mimeHeadersTable.get (l_hashKey); + + if (l_hdr == null) + setHeader (name, value); + else + { + StringBuffer l_oldvalue = new StringBuffer (l_hdr.getValue()); + l_oldvalue.append (value); + Header l_newhdr = new Header (name, l_oldvalue.toString()); + m_mimeHeadersTable.put (l_hashKey, l_newhdr); + } + } + + /** + * Returns the value of the requested header. + * Returns NULL if the header is not present. + * @param name Name of the header field. + * @exception MIMEException If name passed is a NULL. + */ + public String getHeader (String name) throws MIMEException + { + if (name == null) + throw new MIMEException ("Invalid NULL Header name"); + + String l_hashKey = name.toLowerCase(); + Header l_hdr = (Header) m_mimeHeadersTable.get (l_hashKey); + + if (l_hdr != null) + return (l_hdr.getValue()); + else + return (null); + } + + /** + * Deletes the requested header from this Part. + * @param name Name of the header field. + * @exception MIMEException if name is NULL. + */ + public void deleteHeader (String name) throws MIMEException + { + if (name == null) + throw new MIMEException ("Invalid NULL Header name"); + + String l_hashKey = name.toLowerCase(); + m_mimeHeadersTable.remove (l_hashKey); + } + + /** + * Returns all the headers in this Part as an array of Header objects. + * @exception MIMEException If no headers exist. + */ + public Header[] getAllHeaders () throws MIMEException + { + int l_numElements = m_mimeHeadersTable.size(); + int i = 0; + + if (l_numElements <= 0) + return (null); + + Header[] hdrs = new Header [l_numElements]; + Enumeration he = m_mimeHeadersTable.elements(); + + while (he.hasMoreElements()) + { + hdrs[i++] = (Header) he.nextElement(); + } + + return (hdrs); + } + + /** + * Checks if it is OK to set the header with the setHeader(). + * @return null if it is OK to set. Name of the method to use if it is NOT ok. + */ + private String isOKtoset (String name) + { + if (name.equalsIgnoreCase ("Content-Type")) + return ("setContentType()"); + + if (name.equalsIgnoreCase ("Content-ID")) + return ("setContentID()"); + + if (name.equalsIgnoreCase ("Content-MD5")) + return ("setContentMD5()"); + + if (name.equalsIgnoreCase ("Content-Disposition")) + return ("setContentDisposition()"); + + if (name.equalsIgnoreCase ("Content-Description")) + return ("setContentDescription()"); + + if (name.equalsIgnoreCase ("Content-Transfer-Encoding")) + return ("setContentEncoding()"); + + return (null); + + } // end isOKtoset () + + private ByteBuffer getStreamData (InputStream is) + { + ByteBuffer l_retbuf, l_readbuf = new ByteBuffer (DATABUFSZ); + + try + { + if (m_dataStream.markSupported()) + m_dataStream.reset(); + int l_read = is.read (l_readbuf.buffer, 0, DATABUFSZ); + + if (l_read > 0) + { + l_retbuf = new ByteBuffer(DATABUFSZ); // initial size + } + else + return null; + + while (l_read > 0) + { + l_readbuf.hi = l_read; + l_retbuf.append (l_readbuf); + l_read = is.read (l_readbuf.buffer, 0, DATABUFSZ); + } + + if (m_dataStream.markSupported()) + m_dataStream.reset(); + return l_retbuf; + } + catch (IOException e) + { + return null; + } + } + + /** + * Clones an instance of this MIMEBasicPart object. + * @exception CloneNotSupportedException If thrown by constituent components. + */ + public Object clone () throws CloneNotSupportedException + { + // Do the bit-wise copy first. + MIMEBasicPart l_theClone = (MIMEBasicPart) super.clone(); + + // Take care of all other "reference"s. + if (m_mimeHeadersTable != null) + l_theClone.m_mimeHeadersTable = (Hashtable) m_mimeHeadersTable.clone(); + + if (m_databuf != null) + l_theClone.m_databuf = (ByteBuffer) m_databuf.clone(); + else if (m_dataStream != null) + { + l_theClone.m_databuf = getStreamData (m_dataStream); + l_theClone.m_dataStream = null; + } + if (m_contentSubType != null) + l_theClone.m_contentSubType = m_contentSubType; + if (m_contentTypeParams != null) + l_theClone.m_contentTypeParams = m_contentTypeParams; + + if (m_contentID != null) + l_theClone.m_contentID = m_contentID; + + if (m_contentMD5 != null) + l_theClone.m_contentMD5 = m_contentMD5; + + if (m_contentDispParams != null) + l_theClone.m_contentDispParams = m_contentDispParams; + + if (m_contentDescription != null) + l_theClone.m_contentDescription = m_contentDescription; + + l_theClone.m_nStartMessageDataIndex = m_nStartMessageDataIndex; + l_theClone.m_nEndMessageDataIndex = m_nEndMessageDataIndex; + l_theClone.m_nMessageDataLen = m_nMessageDataLen; + l_theClone.m_UserObject = m_UserObject; + l_theClone.m_bDecodedData = m_bDecodedData; + + return (l_theClone); + + } // end clone() + + + protected int getStartMessageDataIndex() { return m_nStartMessageDataIndex; } + protected void setStartMessageDataIndex( int i ) { m_nStartMessageDataIndex = i; } + + protected int getEndMessageDataIndex() { return m_nEndMessageDataIndex; } + protected void setEndMessageDataIndex( int i ) { m_nEndMessageDataIndex = i; } + + public int getMessageDataLen() { return getSize(); } + + protected void setMessageDataLen( int len ) { setSize( len ); } + + protected void setContentType( int contentType ) { m_contentType = contentType; } + + protected boolean getDecodedData() { return m_bDecodedData; } + protected void setDecodedData( boolean decodedData ) { m_bDecodedData = decodedData; } + + protected void setSize( int len ) + { + m_nMessageDataLen = len; + + if (m_databuf != null) + m_databuf.setSize( len ); + //else if (m_dataStream != null) + // m_nMessageDataLen = len; + } + +} // end class MIMEBasicPart diff --git a/msgsdk/java/protocol/MIME/src/MIMEBodyPart.java b/msgsdk/java/protocol/MIME/src/MIMEBodyPart.java new file mode 100644 index 00000000000..66c17085e67 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEBodyPart.java @@ -0,0 +1,281 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; +import java.io.*; +import java.lang.*; +import java.util.*; + + +/** + * The MIMEBodyPart class represents a body-part of a MIME message. + * MIMEBodyPart is the base class for all MIME BodyPart classes: + * MIMEBasicPart, MIMEMessagePart, and MIMEMultiPart. + * @author Prasad Yendluri + */ +public abstract class MIMEBodyPart implements Cloneable +{ + /** + * Content Disposition is Attachment. + */ + public static final int ATTACHMENT = 0; + /** + * Content Disposition is inline. + */ + public static final int INLINE = 1; + + + /** + * Base64 Transfer Encoding. + */ + public static final int BASE64 = 0; + /** + * Quoted Printable Transfer Encoding. + */ + public static final int QP = 1; + /** + * Binary Data with No Transfer Encoding. + */ + public static final int BINARY = 2; + /** + * 7bit Data with No Transfer Encoding. + */ + public static final int E7BIT = 3; + /** + * 8bit Data with No Transfer Encoding. + */ + public static final int E8BIT = 4; + + +//=========================================================================== +// +// Internal members not visible at the API +// +//=========================================================================== + + public static final int UNINITIALIZED = -1; + protected static final int DATABUFSZ = 8192; + protected static final int HDRBUFSZ = 1024; + + protected String m_contentSubType; + protected String m_contentTypeParams; + protected String m_contentID; + protected int m_contentDisposition; + protected String m_contentDispParams; + protected String m_contentDescription; + + protected static final byte[] CRLF = "\r\n".getBytes(); + protected static final byte[] LF = "\n".getBytes(); + protected Object m_UserObject; + + +//=========================================================================== +// +// get() and set() Methods +// +//=========================================================================== + + /** + * Returns Content-Type of this MIME Part. + * Note: The primary content-type is determined by the + * actual MIMEPart class and thus cannot be set. + * @return String containing the Content-Type or + * NULL if Content-Type cannot be determined. + */ + public abstract String getContentType (); + + /** + * Returns Content SubType of this MIME Part. + * @return String containing the Content-SubType or + * NULL if Content-SubType cannot be determined. + */ + public String getContentSubType () + { + if (m_contentSubType == null) + return (null); + + return (m_contentSubType); + } + + /** + * Sets the Content SubType of this MIME Part. + * Note: The primary content-type is determined by the + * actual MIMEPart class and thus cannot be set. + * @param subType String to use as sub-type for this part. + * @exception MIMEException If the subtype parameter is null. + */ + public void setContentSubType (String subType) throws MIMEException + { + // It is client's responsibility to check the consistency with content-type etc. + // We can not really enforce this as, new sub-types can be added any time. + + if (subType != null) + m_contentSubType = subType; + else + throw new MIMEException ("Invalid null Content-SubType"); + } + + /** + * Returns Content-Type Parameters of this MIME Part. + * @return String containing the Content-Type Parameters or + * NULL if no parameters exist. + */ + public String getContentTypeParams () + { + if (m_contentTypeParams == null) + return (null); + + return (m_contentTypeParams); + } + + /** + * Sets Content-Type Parameters of this MIME Part. + * @param params String to use as Content-Type Parameters. + */ + public void setContentTypeParams (String params) + { + // It is client's responsibility to check the consistency with content-type etc. + // We can not really enforce this as, new kind of params can be added any time. + + if (params != null) + m_contentTypeParams = params; + else + m_contentTypeParams = null; + } + + /** + * Returns Content-ID of this MIME Part. + * @return String containing the Content-ID Parameters or + * NULL if none is present. + */ + public String getContentID () + { + if (m_contentID == null) + return (null); + + return (m_contentID); + } + + /** + * Sets Content-ID of this MIME Part. + * @param cid String to use as Content-ID. + */ + public void setContentID (String cid) + { + if (cid != null) + m_contentID = cid; + else + m_contentID = null; + } + + /** + * Returns Content-Disposition of this MIME Part. + * @return Content-Disposition of this MIME Part or + * -1 if none present. + * @see MIMEBodyPart#ATTACHMENT + * @see MIMEBodyPart#INLINE + */ + public int getContentDisposition () + { + if (m_contentDisposition == UNINITIALIZED) + return (-1); + else + return (m_contentDisposition); + } + + /** + * Sets Content-Disposition of this MIME Part. + * @param disposition Value of the Content-Disposition. Must be ATTACHMENT or INLINE. + * @see MIMEBodyPart#ATTACHMENT + * @see MIMEBodyPart#INLINE + * @exception MIMEException If invalid disposition value is passed. + */ + public void setContentDisposition (int disposition) throws MIMEException + { + if ((disposition != MIMEBodyPart.ATTACHMENT) && (disposition != MIMEBodyPart.INLINE)) + { + throw new MIMEException ("Invalid ContentDisposition: " + disposition); + } + + m_contentDisposition = disposition; + } + + /** + * Returns Content-Disposition Parameters of this MIME Part. + * @return String containing the Content Disposition Parameters or + * NULL if none exist. + */ + public String getContentDispParams () + { + if (m_contentDispParams == null) + return (null); + + return (m_contentDispParams); + } + + /** + * Sets Content-Disposition Parameters of this MIME Part. + * @param params String to be used as Content-Disposition Parameters. + */ + public void setContentDispParams (String params) + { + if (params != null) + m_contentDispParams = params; + else + m_contentDispParams = null; + } + + /** + * Returns Content-Description of this MIME Part. NULL if none present. + */ + public String getContentDescription () + { + if (m_contentDescription == null) + return (null); + + return (m_contentDescription); + } + + /** + * Sets Content-Description of this MIME Part. + * @param description String to be used as Content-Description. + */ + public void setContentDescription (String description) + { + if (description != null) + m_contentDescription = description; + else + m_contentDescription = null; + } + + public Object clone () throws CloneNotSupportedException + { + return (super.clone()); + } + + protected Object getUserObject() { return m_UserObject; } + protected void setUserObject( Object userObject ) { m_UserObject = userObject; } + +} diff --git a/msgsdk/java/protocol/MIME/src/MIMEDataSink.java b/msgsdk/java/protocol/MIME/src/MIMEDataSink.java new file mode 100644 index 00000000000..6d80483a384 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEDataSink.java @@ -0,0 +1,256 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + + +package netscape.messaging.mime; + +import java.io.*; +import java.lang.*; +import java.util.*; + +/** + * The MIMEDataSink class represents the DataSink that implements + * callbacks. Clients can subclass from this abstract class. + * @author Carson Lee + */ +abstract public class MIMEDataSink +{ + /** + * Default constructor + */ + public MIMEDataSink () + { + } + + + /** + * Callback that supplies header information. + * @param callbackObject Client-supplied opaque object. + * @param name Name of the header. + * @param value Value of the header. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void header (Object callbackObject, byte[] name, byte[] value ){} + + /** + * Callback that supplies additional value for a header. + * @param callbackObject Client-supplied opaque object. + * @param name Name of the header. + * @param value Value of the header. + * @see #header + */ + public void addHeader (Object callbackObject, byte[] name, byte[] value ){} + + + /** + * Callback to indicate end of headers on the top level message. + * @param callbackObject Client-supplied opaque object. + * @see #startMessage + * @see #header + */ + public void endMessageHeader (Object callbackObject){} + + + /** + * Callback that supplies contentType information. + * @param callbackObject Client-supplied opaque object. + * @param nContentType Content type. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + //public void contentType( Object callbackObject, int nBodyPartType){} + public void contentType( Object callbackObject, byte[] contentType){} + + /** + * Callback that supplies contentSubType information. + * @param callbackObject Client-supplied opaque object. + * @param contentSubType Content subtype. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentSubType( Object callbackObject, byte[] contentSubType ){} + + /** + * Callback that supplies contentTypeParams. + * @param callbackObject Client-supplied opaque object. + * @param contentTypeParams Content type parameters. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentTypeParams( Object callbackObject, byte[] contentTypeParams ){} + + /** + * Callback that supplies ContentID. + * @param callbackObject Client-supplied opaque object. + * @param contentID Content identifier. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentID( Object callbackObject, byte[] ContentID ){} + + /** + * Callback that supplies contentMD5. + * @param callbackObject Client-supplied opaque object. + * @param contentMD5 Content MD5 information. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentMD5( Object callbackObject, byte[] contentMD5 ){} + + /** + * Callback that supplies ContentDisposition. + * @param callbackObject Client-supplied opaque object. + * @param nContentDisposition Content Disposition type. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentDisposition( Object callbackObject, int nContentDisposition ){} + + /** + * Callback that supplies contentDispParams. + * @param callbackObject Client-supplied opaque object. + * @param contentDispParams Content Disposition parameters. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentDispParams( Object callbackObject, byte[] contentDispParams ) {} + + /** + * Callback that supplies contentDescription. + * @param callbackObject Client-supplied opaque object. + * @param contentDescription Content Description. + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentDescription( Object callbackObject, byte[] contentDescription ){} + + /** + * Callback that supplies ContentEncoding. + * @param callbackObject Client-supplied opaque object. + * @param nContentEncoding Content Encoding type. For values, see "MIME Encoding Types." + * @see #startMessage + * @see #startBasicPart + * @see #startMultiPart + * @see #startMessagePart + */ + public void contentEncoding( Object callbackObject, int nContentEncoding ){} + + /** + * Callback that indicates start of a new MIMEMessage. + * No reference to MIMEMessage object is kept internally. + * @return Object Client-supplied opaque object to be passed to subsequent callbacks. + * @see #endMessage + */ + public Object startMessage(){ return null; } + + /** + * Callback that indicates end of MIMEMessage. + * @param callbackObject Client-supplied opaque object. + * @see #startMessage + */ + public void endMessage(Object callbackObject) {} + + /** + * Callback that indicates start of a new MIMEBasicPart. + * No reference to MIMEBasicPart object is kept internally. + * @return Object Client-supplied opaque object to be passed to subsequent callbacks. + * @see #endBasicPart + */ + public Object startBasicPart(){ return null; } + + /** + * No reference to MIMEBasicPart object is kept internally. + * @param callbackObject Client-supplied opaque object. + * @param input Input stream for body data. + * @param len Length of buffer + */ + public void bodyData( Object callbackObject, InputStream input, int len ) {} + + /** + * Callback that indicates end of the MIMEBasicPart. + * @param callbackObject Client-supplied opaque object. + * @see #startBasicPart + */ + public void endBasicPart(Object callbackObject) { } + + /** + * Callback that indicates start of a new MIMEMultiPart. + * No reference to MIMEMultiPart object is kept internally. + * @return Object Client-supplied opaque object to be passed to subsequent callbacks. + * @see #endMultiPart + */ + public Object startMultiPart(){ return null; } + + /** + * Callback that suppiles the boundary string. + * @param callbackObject Client-supplied opaque object. + * @param boundary Encapsulation boundary that separates sub-body parts in a MultiPart. + * @see #startMessagePart + */ + public void boundary (Object callbackObject, byte[] boundary ){} + + /** + * Callback that indicates end of the MultiPart. + * @param callbackObject Client-supplied opaque object. + * @see #startMultiPart + */ + public void endMultiPart( Object callbackObject ){} + + /** + * Callback that indicates start of a new MIMEMessagePart. + * No reference to MIMEMessagePart object is kept internally. + * @return Object Client-supplied opaque object to be passed to subsequent callbacks. + * @see #endMessagePart + */ + public Object startMessagePart(){ return null; } + + /** + * Callback that indicates end of the MessagePart. + * @param callbackObject Client-supplied opaque object. + * @see #startMessagePart + */ + public void endMessagePart( Object callbackObject ) {} +} + + diff --git a/msgsdk/java/protocol/MIME/src/MIMEDynamicParser.java b/msgsdk/java/protocol/MIME/src/MIMEDynamicParser.java new file mode 100644 index 00000000000..f51ed7f8851 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEDynamicParser.java @@ -0,0 +1,123 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; + +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import netscape.messaging.mime.log; +import netscape.messaging.mime.MIMEParser; + +/** + * The MIMEDynamicParser class defines the MIME Dynamic Parser. + * @author Carson Lee + * @version 1.0 + * Dec 17,97 + */ + +// @author Carson Lee +// @version %I%, %G% +public final class MIMEDynamicParser +{ + protected MIMEParser p = null; + + /** + * Constructor for the Dynamic Parser. + * @param dataSink User's datasink for callbacks. Cannot be null. + * @param decodeData Whether the parser should decode message body-part data; if + * true, the parser decodes the data; if false, the parser stores or returns raw data. + * @param localStorage Whether the parser should save references to all callback data + * and build up the MIMEMessage that is available as a whole after endParse(). + * If true, the parser manages data locally; if false, the does not save references to + * data supplied through callbacks. + * @return New MIMEParser object + * @exception MIMEException If dataSink is null or an error occurs. + */ + private MIMEDynamicParser( MIMEDataSink dataSink, + boolean decodeData, + boolean localStorage ) throws MIMEException + { + p = new MIMEParser( dataSink, decodeData, localStorage ); + } + + /** + * Constructor for the Dynamic Parser. + * @param dataSink User's datasink for callbacks. Cannot be null. + * @return New MIMEParser object + * @exception MIMEException If dataSink is null or an error occurs. + */ + public MIMEDynamicParser (MIMEDataSink dataSink) throws MIMEException + { + p = new MIMEParser (dataSink, false, false); + } + + + /** + * Begins a new parse cycle and resets parser internal data-structures. + * This method is called when the user wants to initiate message parsing. + * @exception MIMEException If the parser object was not properly set-up. + */ + public void beginParse() throws MIMEException + { + p.beginParse(); + } + + + + + /** + * Parse more incoming data. + * This method can be called several times between beginParse() and endParse(). + * @param input User's input-stream. Source of data for parse operation. + * @exception MIMEException If parser detects MIME format errors. + */ + public void parse( InputStream input ) throws MIMEException + { + p.parse( input ); + } + + + /** + * Parses more incoming data. + * This method can be called several times between beginParse() and endParse(). + * @param inputData User's input. Source of data for parse operation. + * @exception MIMEException If parser detects MIME format errors. + */ + public void parse (byte [] inputData) throws MIMEException + { + parse (new ByteArrayInputStream (inputData)); + } + + + /** + * Ends parse. Tells parser there is no more data to parse. + * Parser winds up parse operation of the message being parsed. + * @exception MIMEException If parser detects MIME format errors. + */ + public void endParse() throws MIMEException + { + p.endParse(); + } +} diff --git a/msgsdk/java/protocol/MIME/src/MIMEException.java b/msgsdk/java/protocol/MIME/src/MIMEException.java new file mode 100644 index 00000000000..d2cdb079891 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEException.java @@ -0,0 +1,57 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; +import java.io.*; + +/** + * The MIMEException class represents an internal error in + * the MIME API implementation of the Messaging Access SDK. + * A MIMEException is thrown when the Messaging Access SDK + * detects a MIME API error condition. + * @author Prasad Yendluri + */ +public class MIMEException extends Exception { + + /** + * Creates a MIMEException object. + * Default constructor for the MIMEException class. + */ + public MIMEException() { + super(); + } + + + /** + * Creates a MIMEException object given a descriptive string. + * @param s String that describes the exception. + */ + public MIMEException(String s) + { + super(s); + netscape.messaging.mime.log.errorLog(s); + } +} + diff --git a/msgsdk/java/protocol/MIME/src/MIMEHelper.java b/msgsdk/java/protocol/MIME/src/MIMEHelper.java new file mode 100644 index 00000000000..20d0eb42204 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEHelper.java @@ -0,0 +1,2243 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; + + +/** + * The MIMEHelper class defines a set of utility functions. + * @author Prasad Yendluri, Carson Lee + */ +public class MIMEHelper +{ + // error messages + final static public String szERROR_BAD_PARAMETER = "Error: Bad parameter"; + final static public String szERROR_OUT_OF_MEMORY = "Error: Out of memory"; + final public static String szERROR_EMPTY_MESSAGE = "Error: Empty message"; + final public static String szERROR_BAD_MIME_MESSAGE = "Error: Bad mime message"; + final public static String szERROR_BAD_EXTERNAL_MESSAGE_PART = "Error: No External headers in Message/external-body"; + final public static String szERROR_UNSUPPORTED_PARTIAL_SUBTYPE = "Error: Unsupported Partial SubType"; + final public static String szINVALID_CODE = "is an invalid code"; + protected static final int BUFSZ = 4096; + + private static final byte base64map [] = + {// 0 1 2 3 4 5 6 7 + 'A','B','C','D','E','F','G','H', // 0 + 'I','J','K','L','M','N','O','P', // 1 + 'Q','R','S','T','U','V','W','X', // 2 + 'Y','Z','a','b','c','d','e','f', // 3 + 'g','h','i','j','k','l','m','n', // 4 + 'o','p','q','r','s','t','u','v', // 5 + 'w','x','y','z','0','1','2','3', // 6 + '4','5','6','7','8','9','+','/' // 7 + }; + + private static final byte hexmap [] = + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + private static final byte CR = '\r'; + private static final byte LF = '\n'; + private static final byte EQ = '='; + private static final byte HT = '\t'; + private static final byte[] CRLF = "\r\n".getBytes(); + private static final byte[] EQCRLF = "=\r\n".getBytes(); + private static final byte[] EQQ = "=?".getBytes(); + private static final byte[] QEQ = "?=".getBytes(); + private static final byte[] QB64Q = "?B?".getBytes(); + private static final byte[] QQPQ = "?Q?".getBytes(); + + + // Generate base64 decode mapping + private static byte[] Base64DecMap; + static + { + Base64DecMap = new byte[128]; + + for ( int idx = 0; idx < base64map.length; idx++ ) + Base64DecMap[ base64map[idx] ] = (byte) idx; + } + +//============= BEGIN NEW ONE + + /** + * Base64 Encodes data from InputStream and writes to OutputStream. + * @param input InputStream that supplies the data to be encoded. + * @param output OutputStream that accepts the encoded data. + * @return Number of bytes written. + * @exception MIMEException If an encoding error occurs. + * @exception IOException If an I/O error occurs. + */ + public static long encodeBase64 (InputStream input, + OutputStream output) throws MIMEException, IOException + { + byte buf[] = new byte [3]; + byte a, b, c; + int read =0, offset=0, nullCount = 0, linelen = 0, written = 0, readlen = 0, leftover=0; + byte l_bufenc[] = new byte [80]; + byte l_readbuf[] = new byte [BUFSZ]; + int triggerCount = 0; + + + while (true) + { + readlen = input.read (l_readbuf, 0, BUFSZ); + + // trim off the last potential nulls + if (readlen < BUFSZ && readlen > 0) + { + while (l_readbuf [readlen-1] == 0x00) + { + readlen--; + if (readlen == 0) + break; + //System.out.println ("decrementing readlen =" + readlen); + } + } + + if (readlen <= 0) + { + if (leftover == 0) + { + break; + } + else if (leftover == 2) + { + a = buf[0]; + b = buf[1]; + c = 0; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); + l_bufenc[linelen++] = EQ; + written += 4; + break; + } + else if (leftover == 1) + { + a = buf[0]; + b = 0; + c = 0; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = EQ; + l_bufenc[linelen++] = EQ; + written += 4; + break; + } + } + + offset = 0; + + while (readlen > 0) + { + if (leftover > 0) + { + if (leftover == 2) + { + buf [2] = l_readbuf [offset++]; + readlen -= 1; + read = 3; + leftover = 0; + } + else if (leftover == 1) + { + buf [1] = l_readbuf [offset++]; + readlen -= 1; + if (readlen > 0) + { + buf [2] = l_readbuf [offset++]; + readlen -= 1; + read = 3; + leftover = 0; + } + else + { + leftover = 2; + continue; + } + } + + } + else if (readlen >= 3) + { + buf [0] = l_readbuf [offset++]; + buf [1] = l_readbuf [offset++]; + buf [2] = l_readbuf [offset++]; + readlen -= 3; + read = 3; + leftover = 0; + } + else if (readlen == 2) + { + buf [0] = l_readbuf [offset++]; + buf [1] = l_readbuf [offset++]; + readlen = 0; + leftover = 2; + continue; + } + else if (readlen == 1) + { + buf [0] = l_readbuf [offset++]; + readlen = 0; + leftover = 1; + continue; + } + + if ((read == 3 && buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x00) || + (read == 2 && buf[0] == 0x00 && buf[1] == 0x00) || + (read == 1 && buf[0] == 0x00)) + { + nullCount += read; + } + else + nullCount = 0; + + //if (read == 3 && !(nullCount > 100)) + if (read == 3) + { + a = buf[0]; + b = buf[1]; + c = buf[2]; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); + l_bufenc[linelen++] = (base64map[c & 0x3F]); + //linelen += 4; + written += 4; + } + //else if (read == 2 && !(nullCount > 100)) + else if (read == 2) + { + a = buf[0]; + b = buf[1]; + c = 0; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); + l_bufenc[linelen++] = EQ; + //linelen += 4; + written += 4; + } + //else if (read == 1 && !(nullCount > 100)) + else if (read == 1) + { + a = buf[0]; + b = 0; + c = 0; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = EQ; + l_bufenc[linelen++] = EQ; + //linelen += 4; + written += 4; + } + + if (linelen > 71) + { + //output.write (LF); + //written += 1; + l_bufenc[linelen++] = CR; + l_bufenc[linelen++] = LF; + //linelen += 2; + written += 2; + output.write (l_bufenc, 0, linelen); + linelen = 0; + } + + } // end while + } // end outer while + + // write out the leftover stuff + if (linelen > 0) + { + l_bufenc[linelen++] = CR; + l_bufenc[linelen++] = LF; + written += 2; + //l_bufenc[linelen++] = CR; + //l_bufenc[linelen++] = LF; + //written += 4; + //linelen += 4; + output.write (l_bufenc, 0, linelen); + + triggerCount += linelen; + if (triggerCount > 1000000) + { + System.gc(); + //output.flush(); + triggerCount = 0; + } + + linelen = 0; + } + + return (written); + } +//============= END NEW ONE + +//============= BEGIN OLD ONE + /** + * Base64 Encodes data from InputStream and writes to OutputStream. + * @param input InputStream that supplies the data to be encoded. + * @param output OutputStream that accepts the encoded data. + * @return Number of bytes written. + * @exception MIMEException If an encoding error occurs. + * @exception IOException If an I/O error occurs. + */ + protected static long encodeBase64O (InputStream input, + OutputStream output) throws MIMEException, IOException + { + byte buf[] = new byte [3]; + byte a, b, c; + int nullCount = 0, linelen = 0, written = 0, dummy[] = new int[2]; + byte l_bufenc[] = new byte [80]; + int triggerCount = 0; + + while (true) + { + int read = input.read (buf, 0, 3); + + if (read <= 0) + { + break; + } + + if ((read == 3 && buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x00) || + (read == 2 && buf[0] == 0x00 && buf[1] == 0x00) || + (read == 1 && buf[0] == 0x00)) + { + nullCount += read; + } + else + nullCount = 0; + + //if (read == 3 && !(nullCount > 100)) + if (read == 3) + { + a = buf[0]; + b = buf[1]; + c = buf[2]; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); + l_bufenc[linelen++] = (base64map[c & 0x3F]); + //linelen += 4; + written += 4; + } + //else if (read == 2 && !(nullCount > 100)) + else if (read == 2) + { + a = buf[0]; + b = buf[1]; + c = 0; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + if (b != 0x00) + { + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = (base64map[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]); + } + else + l_bufenc[linelen++] = EQ; + l_bufenc[linelen++] = EQ; + //linelen += 4; + written += 4; + } + //else if (read == 1 && !(nullCount > 100)) + else if (read == 1) + { + a = buf[0]; + b = 0; + c = 0; + l_bufenc[linelen++] = (base64map[(a >>> 2) & 0x3F]); + l_bufenc[linelen++] = (base64map[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]); + l_bufenc[linelen++] = EQ; + l_bufenc[linelen++] = EQ; + //linelen += 4; + written += 4; + } + + if (linelen > 71) + { + //output.write (LF); + //written += 1; + l_bufenc[linelen++] = CR; + l_bufenc[linelen++] = LF; + //linelen += 2; + written += 2; + output.write (l_bufenc, 0, linelen); + linelen = 0; + } + + } // end while + + // write out the leftover stuff + if (linelen > 0) + { + l_bufenc[linelen++] = CR; + l_bufenc[linelen++] = LF; + l_bufenc[linelen++] = CR; + l_bufenc[linelen++] = LF; + //linelen += 4; + written += 4; + output.write (l_bufenc, 0, linelen); + + triggerCount += linelen; + if (triggerCount > 1000000) + { + System.gc(); + //output.flush(); + triggerCount = 0; + } + + linelen = 0; + } + + return (written); + } +//============= END OLD ONE + + + /** + * QuotedPrintable Encodes data from InputStream and writes to OutputStream. + * @param input InputStream that supplies the data to be encoded. + * @param output OutputStream that accepts the encoded data. + * @return Number of bytes written. + * @exception MIMEException If an encoding error occurs. + * @exception IOException If an I/O error occurs. + */ + public static long encodeQP (InputStream input, + OutputStream output) throws MIMEException, IOException + { + byte current = (byte)0, previous = (byte)0; + int read, linelen = 0, written = 0, lastspace = 0, nullCount = 0; + byte l_bufenc[] = new byte [80]; + + while (true) + { + read = input.read(); + + if (read == -1) + { + if (linelen > 0) + { + output.write (l_bufenc, 0, linelen); + output.write (CRLF); + written += 2; + } + + return (written); + } + + current = (byte) read; + + if (current == 0x00) + { + nullCount++; + previous = current; + lastspace = 0; + continue; + } + else if (nullCount > 0) + { + // write out all the nulls first and fall through to process current char. + for (int idx = 1; idx <= nullCount ; idx++) + { + byte tmp = 0x00; + l_bufenc [linelen++] = EQ; + l_bufenc [linelen++] = (byte) hexmap [(tmp >>> 4) & 0xF]; + l_bufenc [linelen++] = (byte) hexmap [(tmp & 0xF)]; + // l_bufenc [linelen++] = (byte)0x00; + // l_bufenc [linelen++] = (byte)0x00; + written += 3; + + if (linelen > 74) + { + output.write (l_bufenc, 0, linelen); + output.write (EQCRLF); + written += 3; + linelen = 0; + } + } + + previous = (byte) 0; + nullCount = 0; + } + + if ((current > ' ') && (current < 0x7F) && (current != '=')) + { + // Printable chars + //output.write ((byte) current); + l_bufenc [linelen++] = (byte) current; + //linelen += 1; + written += 1; + lastspace = 0; + previous = current; + } + else if ((current == ' ') || (current == HT)) + { + //output.write ((byte) current); + l_bufenc [linelen++] = (byte) current; + //linelen += 1; + written += 1; + lastspace = 1; + previous = current; + } + else if ((current == LF) && (previous == CR)) + { + // handled this already. Ignore. + previous = (byte) 0; + } + else if ((current == CR ) || (current == LF)) + { + // Need to emit a soft line break if last char was SPACE/HT or + // if we have a period on a line by itself. + if ((lastspace == 1) || ((previous == '.') && (linelen == 1))) + { + l_bufenc [linelen++] = EQ; + l_bufenc [linelen++] = CR; + l_bufenc [linelen++] = LF; + written += 3; + } + + l_bufenc [linelen++] = CR; + l_bufenc [linelen++] = LF; + lastspace = 0; + written += 2; + output.write (l_bufenc, 0, linelen); + previous = (byte) 0; + linelen = 0; + //output.write (CRLF); + //previous = current; + } + else if ( (current < ' ') || (current == '=') || (current >= 0x7F) ) + { + // Special Chars + //output.write ((byte) '='); + //output.write ((byte) hexmap [(current >>> 4)]); + //output.write ((byte) hexmap [(current & 0xF)]); + l_bufenc [linelen++] = EQ; + + l_bufenc [linelen++] = (byte) hexmap [(current >>> 4) & 0xF]; + l_bufenc [linelen++] = (byte) hexmap [(current & 0xF)]; + lastspace = 0; + //linelen += 3; + written += 3; + previous = current; + } + else + { + //output.write ((byte) current); + l_bufenc [linelen++] = (byte) current; + lastspace = 0; + //linelen += 1; + written += 1; + previous = current; + } + + if (linelen > 74) + { + + output.write (l_bufenc, 0, linelen); + output.write (EQCRLF); + written += 3; + linelen = 0; + previous = (byte) 0; + } + + } // while + + } //encodeQP + + + /** + * Q Encodes data from InputStream and writes to OutputStream. + * @param input InputStream that supplies the data to be encoded. + * @param output OutputStream that accepts the encoded data. + * @return Number of bytes written. + * @exception MIMEException If an encoding error occurs. + * @exception IOException If an I/O error occurs. + */ + private static long encodeQ (InputStream input, + OutputStream output) throws MIMEException, IOException + { + byte current = (byte)0; + boolean encode = false; + int read, written = 0; + + while (true) + { + read = input.read(); + + if (read == -1) + { + return (written); + } + + current = (byte) read; + + if ((current >= 0x30 && current <= 0x39) || // 0-9 + (current >= 0x41 && current <= 0x5A) || // A-Z + (current >= 0x61 && current <= 0x7A) || // a-z + (current == 0x21 || current == 0x2A) || // !* + (current == 0x2B || current == 0x2D) || // +- + (current == 0x2F || current == 0x5F)) // /_ + { + encode = false; + } + else + encode = true; + + if (encode) + { + // Special Chars + output.write ((byte) '='); + output.write ((byte) hexmap [(current >>> 4)]); + output.write ((byte) hexmap [(current & 0xF)]); + written += 3; + } + else + { + output.write ((byte) current); + written += 1; + } + + } // while + } + + + /** + * String version. Base64 Decodes data from the input string. + * @param str The base64-encoded string. + * @return The decoded string + */ + protected final static String decodeBase64( String str ) + { + if (str == null) + return null; + +// byte data[] = new byte[str.length()]; +// str.getBytes(0, str.length(), data, 0); + + return new String( decodeBase64( str.getBytes() ) ); + } + + + /** + * byte[] version. Base64 Decodes input bytes. + * @param data base64-encoded bytes. + * @return the decoded bytes + */ + protected final static byte[] decodeBase64( byte[] data ) + { + return decodeBase64( data, data.length ); + } + + + // byte[] version, ByteBuffer version + // carsonl + /** + * byte[] version. Base64 Decodes input bytes of given length. + * @param data base64-encoded bytes. + * @param len length of base64-encoded bytes. + * @return the decoded bytes + */ + protected final static byte[] decodeBase64( byte[] data, int len ) + { + if (data == null) + return null; + + while (data[len-1] == '=') + len--; + + byte dest[] = new byte[len - data.length/4]; + + + // ascii printable to 0-63 conversion + for (int idx = 0; idx >> 4) & 003) ); + + dest[didx+1] = (byte) ( ((data[sidx+1] << 4) & 255) | + ((data[sidx+2] >>> 2) & 017) ); + + dest[didx+2] = (byte) ( ((data[sidx+2] << 6) & 255) | + (data[sidx+3] & 077) ); + } + + if (didx < dest.length) + dest[didx] = (byte) ( ((data[sidx] << 2) & 255) | ((data[sidx+1] >>> 4) & 003) ); + + if (++didx < dest.length) + dest[didx] = (byte) ( ((data[sidx+1] << 4) & 255) | ((data[sidx+2] >>> 2) & 017) ); + + return dest; + } + + + + /** + * QuotedPrintable Decodes data from InputStream and writes to OutputStream + * @param input InputStream that supplies the data to be decoded. + * @param output OutputStream that accepts the decoded data. + * @exception MIMEException if a decoding error occurs. + * @exception IOException if io error occurs. + */ + public static void decodeQP (InputStream input, OutputStream output) throws MIMEException + { + byte inputBuffer[]; + + try + { + inputBuffer = new byte[ input.available() + 1 ]; + input.read( inputBuffer ); + output.write( decodeQP( inputBuffer ) ); + } + catch ( IOException e ) + { + throw new MIMEException ( e.getMessage() ); + } + catch ( Exception e ) + { + throw new MIMEException ( e.getMessage() ); + } + + return; + } + + + /** + * String version. QP decodes data from the input string. + * @param str the QP-encoded string. + * @return the decoded string + */ + protected final static String decodeQP( String str ) throws MIMEException + { + String decodedString = null; + + if (str == null) + return null; + +// byte data[] = new byte[str.length()]; +// str.getBytes(0, str.length(), data, 0); + + try + { + decodedString = new String( decodeQP( str.getBytes() ) ); + } + catch( Exception e ) + { + throw new MIMEException( e.getMessage() ); + } + + return decodedString; + } + + + + /** + * byte[] version. QP decodes input bytes. + * @param bytesIn QP-encoded bytes. + * @return the decoded bytes + */ + protected final static byte[] decodeQP( byte[] bytesIn ) throws MIMEException + { + return decodeQP( bytesIn, bytesIn.length ); + } + + + /** + * byte[] version. QP decodes input bytes of given length. + * @param bytesIn QP-encoded bytes. + * @param len length of QP-encoded bytes. + * @return the decoded bytes + * @exception ParseException If a '=' is not followed by a valid + * 2-digit hex number or '\r\n'. + */ + protected final static byte[] decodeQP( byte[] bytesIn, int len ) throws MIMEException + { + if ( bytesIn == null ) + throw new MIMEException( szERROR_BAD_PARAMETER ); + + byte res[] = new byte[ (int) ( len * 1.1) ]; + byte src[] = bytesIn; + byte nl[] = System.getProperty("line.separator", "\n").getBytes(); + + int last = 0, + j = 0; + + for (int i = 0; i < len; ) + { + byte ch = src[i++]; + + if (ch == '=') + { + if (src[i] == '\n' || src[i] == '\r') + { // Rule #5 + i++; + + if (src[i-1] == '\r' && src[i] == '\n') + i++; + } + + else // Rule #1 + { + byte repl; + int hi = Character.digit( (char) src[i], 16), + lo = Character.digit( (char) src[i+1], 16); + + if ((hi | lo) < 0) + throw new MIMEException( new String(src, i-1, 3) + szINVALID_CODE ); + else + { + repl = (byte) (hi << 4 | lo); + i += 2; + } + + res[j++] = repl; + } + + last = j; + } + else if (ch == '\n' || ch == '\r') // Rule #4 + { + if (src[i-1] == '\r' && src[i] == '\n') + i++; + + for (int idx = 0; idx < nl.length; idx++) + res[last++] = nl[idx]; + + j = last; + } + + else // Rule #1, #2 + { + res[j++] = ch; + + if (ch != ' ' && ch != '\t') // Rule #3 + last = j; + } + + if ( j > res.length - 5 ) + { + byte tmp[] = new byte[ res.length + 500 ]; + System.arraycopy( res, 0, tmp, 0, res.length ); + res = tmp; + } + } + + return res; + } + + /** + * Encodes a string that typically goes in a header but uses characters other than ASCII + * per RFC2047. Can be used on unstructured rfc822 headers or comments of structured ones. + * The returned string can be passed to setHeader() method in MIMEMessage class. + * @param inputString The string to encode. + * @param charsetName Name of the charset the input string is in. If null uses iso-8859-1. + * @param encoding_type Must "B" or "Q". + * @return The encoded header string. + * @exception MIMEException if an encoding error occurs. + */ + public static String encodeHeader (String inputString, + String charsetName, + String encoding_type) throws MIMEException, UnsupportedEncodingException + { + int l_encoding = -1; // 0 = b64, 1= Q + int l_inlength = 0; + byte [] l_inbuf; + + if (encoding_type.equalsIgnoreCase ("B")) + l_encoding = 0; + else if (encoding_type.equalsIgnoreCase ("Q")) + l_encoding = 1; + else + throw new MIMEException ("Invalid encoding_type: " + encoding_type); + + if (charsetName == null) + l_inbuf = inputString.getBytes("iso-8859-1"); + else + l_inbuf = inputString.getBytes(charsetName); + + l_inlength = l_inbuf.length; + + + try + { + ByteArrayInputStream ins = new ByteArrayInputStream (l_inbuf); + ByteArrayOutputStream os = new ByteArrayOutputStream (l_inlength * 2); + + long enclen; + if (l_encoding == 0) // base64 + enclen = encodeBase64 (ins, os); + else + enclen = encodeQ (ins, os); + + if (enclen <= 0) + throw new MIMEException (" Header Encoding error: " + + encoding_type); + + //ByteString l_bs64 = new ByteString (os.toByteArray(), 0, (int)enclen-1); + // hi is length of array not last index! --------------------^^^^^^^^^^ + ByteString l_bsenc = new ByteString (os.toByteArray(), 0, (int)enclen); + ByteBuffer l_bufenc = new ByteBuffer (l_bsenc); + + ByteBuffer l_retbuf = new ByteBuffer ((int)enclen+20); + l_retbuf.append (EQQ); + l_retbuf.append (charsetName.getBytes()); + if (l_encoding == 0) // base64 + l_retbuf.append (QB64Q); + else + l_retbuf.append (QQPQ); + l_retbuf.append (l_bsenc); + l_retbuf.append (QEQ); + + return (new String (l_retbuf.buffer)); + } + catch (Exception e) + { + throw new MIMEException (e.getMessage()); + } + } + + /** + * Decodes a string encoded in RFC2047 format. If the string is not encoded + * returns the original string. Can be used on the header value returned by + * getHeader() and getAllHeaders() methods in MIMEMessage class. + * @param inputString The string to decode. + * @return The decoded header string. + * @exception MIMEException If an decoding error occurs. + */ + public static String decodeHeader (String inputString) throws MIMEException + { + int len; + int i, j; + int nStart = 0; + int nEnd = 0; + String szEncodedText; + StringBuffer OutputBuffer = new StringBuffer(); + String plainPart=null; + byte decodedText[] = null; + char achCharset[] = new char[16]; + boolean bEncodeBase64 = false; + boolean bEncodeQP = false; + boolean bEncodeString = false; + + if ( inputString == null ) + throw new MIMEException( szERROR_BAD_PARAMETER ); + + len = inputString.length(); + + /* find start of encoding text */ + for ( i = 0; i < len && inputString.charAt(i) != 0 ; i++ ) + { + if ( inputString.charAt(i) == '=' && inputString.charAt(i+1) == '?' ) + { + bEncodeString = true; + break; + } + } + + if (!bEncodeString) + return inputString; + + if (i > 0) + { + plainPart = inputString.substring (0, i); + } + + /* get charset */ + for ( i += 2, j = 0; i < len && inputString.charAt(i) != '?'; i++, j++ ) + { + achCharset[j] = inputString.charAt(i); + } + + achCharset[j] = 0; + i++; /* skip ? */ + + /* get encoding type */ + if ( inputString.charAt(i) == 'Q' || inputString.charAt(i) == 'q' ) + bEncodeQP = true; + + else if ( inputString.charAt(i) == 'B' || inputString.charAt(i) == 'b' ) + bEncodeBase64 = true; + + if (inputString.charAt(i+1) == '?') + i++; /* skip ? */ + nStart = ++i; + + /* look for end of encoded text */ + for ( j = 0; i < len && inputString.charAt(i) != 0 ; i++, j++ ) + { + if ( inputString.charAt(i) == '?' && i+1 < len && inputString.charAt(i+1) == '=' ) + { + nEnd = i; + break; + } + } + + + if ( nEnd > 0 ) + { + /* extract encoded text */ + szEncodedText = inputString.substring( nStart, nStart + j ); + + if ( bEncodeQP ) + decodedText = decodeQP( szEncodedText.getBytes() ); + + else if ( bEncodeBase64 ) + decodedText = decodeBase64( szEncodedText.getBytes() ); + } + else + { + throw new MIMEException ("Improper encoded String"); + } + + + if (plainPart != null) + { + OutputBuffer.append (plainPart); + } + + if ( decodedText != null ) + { + OutputBuffer.append (new String(decodedText)); + } + + if (nEnd+2 < len-1) + { + String remainder = inputString.substring (nEnd+2, len); + String retString = decodeHeader (remainder); + if (retString != null) + OutputBuffer.append (retString); + } + + return (OutputBuffer.toString()); + + } + + + + /** + * Generates and returns a boundary string that can be used in multi-parts. + * @return The boundary string. + */ + public static String generateBoundary () + { + Random l_rand = new Random (System.currentTimeMillis()); + long l_numboundary = l_rand.nextLong(); + String l_boundary = new String ("-----" + Long.toHexString(l_numboundary)); + return (l_boundary); + } + + + + /** + * Converts the input unicode ASCII printable chars to ASCII bytes. + * @param inputString The string to convert. + * @exception MIMEException If the input contains non-printable ASCII or non-ASCII chars. + */ + public static byte[] unicodeToASCII (String inputString) throws + UnsupportedEncodingException, MIMEException + { + //byte [] l_bytebuf = inputString.getBytes("iso-8859-1"); + byte [] l_bytebuf = inputString.getBytes(); + + for (int i=0, len = l_bytebuf.length; i < len; i++) + { + if ( (l_bytebuf[i] > 126 || l_bytebuf[i] < 32) && + l_bytebuf[i] != CR && l_bytebuf[i] != LF ) + throw new MIMEException ("Invalid non-printable ASCII or non-ASCII character." + l_bytebuf[i]); + } + + return (l_bytebuf); + + } // unicodeToASCII + + /** + * Based on file extension, attempts to determine the file MIME content-type + * sub-type, params and extension. By default returns application/octet-stream. + * @param filename Filename in the form name.ext. If no extension returns application/octet-stream. + * @return Object of class fileMIMEType that has the MIME content-type* info set. + */ + public static fileMIMEType getFileMIMEType (String filename) + { + String inStr, extn; + int strLen, sepIndex; + fileMIMEType fmt = new fileMIMEType(); + + inStr = filename.trim(); + strLen = inStr.length(); + sepIndex = inStr.lastIndexOf ('.'); + extn = inStr.substring (sepIndex+1, strLen); + + fmt.file_extn = extn; + + File ff = new File (filename); + fmt.file_shortname = ff.getName(); + + if (extn.equalsIgnoreCase ("txt") || extn.equalsIgnoreCase ("java") || + extn.equalsIgnoreCase ("c") || extn.equalsIgnoreCase ("C") || + extn.equalsIgnoreCase ("cc") || extn.equalsIgnoreCase ("CC") || + extn.equalsIgnoreCase ("h") || extn.equalsIgnoreCase ("hxx") || + extn.equalsIgnoreCase ("bat") || extn.equalsIgnoreCase ("rc") || + extn.equalsIgnoreCase ("ini") || extn.equalsIgnoreCase ("cmd") || + extn.equalsIgnoreCase ("awk") || extn.equalsIgnoreCase ("html") || + extn.equalsIgnoreCase ("sh") || extn.equalsIgnoreCase ("ksh") || + extn.equalsIgnoreCase ("pl") || extn.equalsIgnoreCase ("DIC") || + extn.equalsIgnoreCase ("EXC") || extn.equalsIgnoreCase ("LOG") || + extn.equalsIgnoreCase ("SCP") || extn.equalsIgnoreCase ("WT") || + extn.equalsIgnoreCase ("mk") || extn.equalsIgnoreCase ("htm")) // what else? + { + fmt.content_type = MIMEBasicPart.TEXT; + if (extn.equalsIgnoreCase ("html") || extn.equalsIgnoreCase ("htm")) + fmt.content_subtype = "html"; + else + fmt.content_subtype = "plain"; + fmt.content_params = "us-ascii"; + fmt.mime_encoding = MIMEBodyPart.E7BIT; + } + else if (extn.equals ("pdf")) // PDF is different + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "pdf"; + } + else if (extn.equalsIgnoreCase ("AIF") || extn.equalsIgnoreCase ("AIFC") || + extn.equalsIgnoreCase ("AIFF")) + { + fmt.content_type = MIMEBasicPart.AUDIO; + fmt.content_subtype = "aiff"; + } + else if (extn.equalsIgnoreCase ("AU") || extn.equalsIgnoreCase ("SND")) + { + fmt.content_type = MIMEBasicPart.AUDIO; + fmt.content_subtype = "basic"; + } + else if (extn.equalsIgnoreCase ("WAV")) + { + fmt.content_type = MIMEBasicPart.AUDIO; + fmt.content_subtype = "wav"; + } + else if (extn.equalsIgnoreCase ("gif") ) + { + fmt.content_type = MIMEBasicPart.IMAGE; + fmt.content_subtype = "gif"; + } + else if (extn.equalsIgnoreCase ("jpg") ) + { + fmt.content_type = MIMEBasicPart.IMAGE; + fmt.content_subtype = "jpeg"; + } + else if (extn.equalsIgnoreCase ("jpeg") ) + { + fmt.content_type = MIMEBasicPart.IMAGE; + fmt.content_subtype = "jpeg"; + } + else if (extn.equalsIgnoreCase ("tif") ) + { + fmt.content_type = MIMEBasicPart.IMAGE; + fmt.content_subtype = "tiff"; + } + else if (extn.equalsIgnoreCase ("XBM") ) + { + fmt.content_type = MIMEBasicPart.IMAGE; + fmt.content_subtype = "x-xbitmap"; + } + else if (extn.equalsIgnoreCase ("avi") ) + { + fmt.content_type = MIMEBasicPart.VIDEO; + fmt.content_subtype = "avi"; + } + else if (extn.equalsIgnoreCase ("mpeg") ) + { + fmt.content_type = MIMEBasicPart.VIDEO; + fmt.content_subtype = "mpeg"; + } + else if (extn.equalsIgnoreCase ("ps") || extn.equalsIgnoreCase ("EPS")) + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "postscript"; + } + else if (extn.equalsIgnoreCase ("tar") ) + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "x-tar"; + } + else if (extn.equalsIgnoreCase ("zip") ) + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "zip"; + } + else if (extn.equalsIgnoreCase ("js") ) + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "x-javascript"; + } + else if (extn.equalsIgnoreCase ("doc") ) + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "msword"; + } + else if (extn.equalsIgnoreCase ("nsc") ) + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "x-conference"; + } + else if (extn.equalsIgnoreCase ("ARC") || extn.equalsIgnoreCase ("ARJ") || + extn.equalsIgnoreCase ("B64") || extn.equalsIgnoreCase ("BHX") || + extn.equalsIgnoreCase ("GZ") || extn.equalsIgnoreCase ("HQX")) + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "x-gzip"; + } + else + { + fmt.content_type = MIMEBasicPart.APPLICATION; + fmt.content_subtype = "octet-stream"; + } + + return (fmt); + } + + +/* + protected static byte[] decodeQPVector( Vector v, int nStart, int nEnd, int nMessageLen, int[] param ) throws MIMEException + { + int i, j, nLen = 0, nTotalLen = 0, last = 0, jj = 0; + byte[] line; + byte[] output; + byte nl[] = System.getProperty("line.separator", "\n").getBytes(); + + if ( v == null || param == null ) + throw new MIMEException( szERROR_BAD_PARAMETER ); + + // get message len + for ( j = nStart; j <= nEnd; j++ ) + { + line = (byte[]) v.elementAt( j ); + nTotalLen = nTotalLen + line.length; + } + + output = new byte[ (int)( nTotalLen * 1.1 ) ]; + + if ( output == null ) + throw new MIMEException( "Not enough memory" ); + + // read off data from vector line by line + for ( j = nStart; j <= nEnd; j++ ) + { + line = (byte[]) v.elementAt( j ); + nLen = line.length; + + for ( i = 0; i < nLen; ) + { + byte ch = line[i++]; + + if (ch == '=') + { + if (line[i] == '\n' || line[i] == '\r') + { // Rule #5 + i++; + + if (line[i-1] == '\r' && line[i] == '\n') + i++; + } + + else // Rule #1 + { + byte repl; + int hi = Character.digit( (char) line[i], 16), + lo = Character.digit( (char) line[i+1], 16); + + if ((hi | lo) < 0) + throw new MIMEException(new String(line, i-1, 3) + "is an invalid code"); + else + { + repl = (byte) (hi << 4 | lo); + i += 2; + } + + output[j++] = repl; + } + + last = jj; + } + else if (ch == '\n' || ch == '\r') // Rule #4 + { + if (line[i-1] == '\r' && line[i] == '\n') + i++; + + for (int idx = 0; idx < nl.length; idx++) + output[last++] = nl[idx]; + + jj = last; + } + + else // Rule #1, #2 + { + output[jj++] = ch; + + if (ch != ' ' && ch != '\t') // Rule #3 + last = jj; + } + + if ( jj > output.length - 5 ) + { + byte tmp[] = new byte[ output.length + 500 ]; + System.arraycopy( output, 0, tmp, 0, output.length ); + output = tmp; + } + + } // for each line + + } // for vector + + param[2] = jj; // len + + return output; + } +*/ + +/* +* carsonl, jan 8,98 +* hex to decimal +* +* parameter : +* +* int nFirstByte : first hex byte +* int nSecondByte : second hex byte +* +* returns : decimal +*/ +static byte nConvertHexToDec( int nFirstByte, int nSecondByte ) +{ + byte nValue = 0; + + switch ( nFirstByte ) + { + case ' ': + case '0': nValue += 0; break; + case '1': nValue += 16; break; + case '2': nValue += 32; break; + case '3': nValue += 48; break; + case '4': nValue += 64; break; + case '5': nValue += 80; break; + case '6': nValue += 96; break; + case '7': nValue += 112; break; + case '8': nValue += 128; break; + case '9': nValue += 144; break; + case 'A': + case 'a': nValue += 160; break; + case 'B': + case 'b': nValue += 176; break; + case 'C': + case 'c': nValue += 192; break; + case 'D': + case 'd': nValue += 208; break; + case 'E': + case 'e': nValue += 224; break; + case 'F': + case 'f': nValue += 240; break; + } + + switch ( nSecondByte ) + { + case ' ': + case '0': nValue += 0; break; + case '1': nValue += 1; break; + case '2': nValue += 2; break; + case '3': nValue += 3; break; + case '4': nValue += 4; break; + case '5': nValue += 5; break; + case '6': nValue += 6; break; + case '7': nValue += 7; break; + case '8': nValue += 8; break; + case '9': nValue += 9; break; + case 'A': + case 'a': nValue += 10; break; + case 'B': + case 'b': nValue += 11; break; + case 'C': + case 'c': nValue += 12; break; + case 'D': + case 'd': nValue += 13; break; + case 'E': + case 'e': nValue += 14; break; + case 'F': + case 'f': nValue += 15; break; + } + + return nValue; +} + + +protected static byte[] decodeQPVectorNew (Vector v, int nStart, int nEnd, + int[] param, byte[] leftOverBytes) throws MIMEException +{ + int i = 0, j = 0, ii = 0, nLen = 0, written=0; + byte[] line, buffer, retbuf; + byte[] token = new byte [3]; + int nNoOfLeftOverBytes; + + if ( v == null || param == null ) + throw new MIMEException( szERROR_BAD_PARAMETER ); + + if ( nStart == -1 || nEnd == -1 ) + return null; + + nNoOfLeftOverBytes = param[0]; + + // get message len + nLen = nNoOfLeftOverBytes; + for (j = nStart; j <= nEnd; j++) + { + line = (byte[]) v.elementAt (j); + nLen = nLen + line.length; + } + + j = 0; + buffer = new byte[(int) (nLen * 1.1)]; + + if (buffer == null) + throw new MIMEException( szERROR_OUT_OF_MEMORY ); + + nLen = 0; + /* get each line */ + for ( ii = nStart; ii <= nEnd; ii++ ) + { + line = (byte[]) v.elementAt(ii); + + if (ii == nStart && nNoOfLeftOverBytes > 0) + { + System.arraycopy (leftOverBytes, 0, buffer, 0, nNoOfLeftOverBytes); + nLen += nNoOfLeftOverBytes; + } + + if (line == null || line.length == 0) + continue; + System.arraycopy (line, 0, buffer, nLen, line.length); + nLen += line.length; + } + + written = 0; i=0; + int read_offset = 0; + + /* first time do it outside the loop */ + while (i < 3 && read_offset < nLen) + { + token [i++] = buffer [read_offset++]; + } + + while (read_offset < nLen || i != 0) + { + while (i < 3 && read_offset < nLen) + { + token [i++] = buffer [read_offset++]; + } + + if (i < 3) /* did not get enough for a token */ + { + if (i == 2 && token [0] == CR && token [1] == LF) + { + buffer[written++] = token[0]; + buffer[written++] = token[1]; + nNoOfLeftOverBytes = 0; + } + else + { + for (j=0; j < i; j++) + leftOverBytes [j] = token [j]; + nNoOfLeftOverBytes = i; + } + + param[0] = nNoOfLeftOverBytes; + + if (written > 0) + { + retbuf = new byte [written]; + System.arraycopy (buffer, 0, retbuf, 0, written); + return (retbuf); + } + } + + i = 0; + + if (token [0] == '=') + { + byte c = (byte)0; + if ((token[1] >= '0' && token[1] <= '9') || + (token[1] >= 'A' && token[1] <= 'F') || + (token[1] >= 'a' && token[1] <= 'f')) + c = token[1]; + else if (token[1] == CR || token[1] == LF) + { + /* =\n means ignore the newline. */ + if (token[1] == CR && token[2] == LF) + ; /* swallow all three chars */ + else + { + read_offset--; /* put the third char back */ + } + continue; + } + else + { + /* = followed by something other than hex or newline - + pass it through unaltered, I guess. + */ + if (read_offset > written) { buffer[written++] = token[0]; } + if (read_offset > written) { buffer[written++] = token[1]; } + if (read_offset > written) { buffer[written++] = token[2]; } + continue; + } + + /* Second hex digit */ + if ((token[2] >= '0' && token[2] <= '9') || + (token[2] >= 'A' && token[2] <= 'F') || + (token[2] >= 'a' && token[2] <= 'f')) + c = (byte) ((c << 4) | token[2]); + else + { + /* We got =xy where "x" was hex and "y" was not, so + treat that as a literal "=", x, and y. */ + if (read_offset > written) { buffer[written++] = token[0];} + if (read_offset > written) { buffer[written++] = token[1];} + if (read_offset > written) { buffer[written++] = token[2];} + continue; + } + + buffer[written++] = c; + } + else + { + buffer[written++] = token[0]; + + token[0] = token[1]; + token[1] = token[2]; + i = 2; + } + } + + param[0] = 0; // nothing leftover + + if (written > 0) + { + retbuf = new byte [written]; + System.arraycopy (buffer, 0, retbuf, 0, written); + return (retbuf); + } + + return null; +} + +/* +* carson, Jan 8,98 +* QuotedPrintable Decodes data from vector, write decoded data to szOutput +* +* params : +* +* int nStart : starting element in vector +* int nEndt : ending element in vector +* Vector *v : vector +* char *szOutput : output buffer +* int MaxBufferSize : max buffer size +* +* return : number of decoded bytes +*/ +protected static byte[] decodeQPVector( Vector v, int nStart, int nEnd, int[] param, byte[] leftOverBytes ) throws MIMEException +{ + byte ch, ch2; + int i = 0; + int j = 0; + int k = 0; + int ii, nLen=0; + byte[] line; + byte[] output; + byte[] buffer; + boolean bAppendLeftOverBytes = false; + int nNoOfLeftOverBytes; + int hi = 0, lo = 0, last = 0; + char nl[] = System.getProperty("line.separator", "\n").toCharArray(); + + if ( v == null || param == null ) + throw new MIMEException( szERROR_BAD_PARAMETER ); + + if ( nStart == -1 || nEnd == -1 ) + return null; + + nNoOfLeftOverBytes = param[0]; + + // get message len + for ( j = nStart; j <= nEnd; j++ ) + { + line = (byte[]) v.elementAt( j ); + nLen = nLen + line.length; + } + + output = new byte[ (int) ( nLen * 1.1 ) ]; + buffer = new byte[ 128 ]; + j = 0; + + if ( output == null || buffer == null ) + throw new MIMEException( szERROR_OUT_OF_MEMORY ); + + /* get each line */ + for ( ii = nStart; ii <= nEnd; ii++ ) + { + line = (byte[]) v.elementAt(ii); + + if ( !bAppendLeftOverBytes ) + { + if ( nNoOfLeftOverBytes > 0 ) + { + System.arraycopy( leftOverBytes, 0, buffer, 0, nNoOfLeftOverBytes ); + System.arraycopy( line, 0, buffer, nNoOfLeftOverBytes, line.length ); + line = buffer; + } + + bAppendLeftOverBytes = false; + } + + if ( line.length == 0 ) + continue; + + /* enumerate through each char */ +// for ( i = 0, ch = line[i]; ch != 0 && i < line.length; ch = line[++i] ) + +/* ---------------- old --------------------------- + for ( i = 0; i < line.length; i++ ) + { + ch = line[i]; + + if ( ( ch >= 33 && ch <= 60 ) || ( ch >= 62 && ch <= 126 ) || ( ch == 9 || ch == 32 ) ) + { + output[j++] = ch; + } + + else if ( ch == '=' ) + { + if ( i + 2 < line.length ) + { + output[j++] = nConvertHexToDec( line[++i], line[++i] ); + } + + // get more data + else if ( ii < nEnd ) + { + + int len2 = line.length - i - 1; + System.arraycopy( line, i, buffer, 0, len2 ); + line = (byte[]) v.elementAt(++ii); + System.arraycopy( line, 0, buffer, len2, line.length ); + line = buffer; + i = -1; + } + + // save it for next time + else + { + leftOverBytes[0] = ch; nNoOfLeftOverBytes = 1; + + if ( i+1 < line.length ) + leftOverBytes[1] = ch; nNoOfLeftOverBytes = 2; + } + } + } +----------- old ------------------------------------ */ + + +/* -------------------------- new ---------------------*/ + + for ( i = 0; i < line.length; ) + { + ch = line[i++]; + + if (ch == '=') + { + if ( i < line.length && ( line[i] == '\n' || line[i] == '\r' ) ) + { // Rule #5 + i++; + + if ( i < line.length && line[i-1] == '\r' && line[i] == '\n') + i++; + } + + else if ( i + 1 < line.length ) // Rule #1 + { + hi = Character.digit( (char) line[i], 16); + lo = Character.digit( (char) line[i+1], 16); + +if ( hi < 0 ) + hi = 0; +if ( lo < 0 ) + lo = 0; + + { + ch2 = (byte) (hi << 4 | lo); + i += 2; + } + + output[j++] = ch2; + } + + // get more data + else if ( ii < nEnd ) + { + int len2 = line.length - i; + System.arraycopy( line, i, buffer, 0, len2 ); + line = (byte[]) v.elementAt(++ii); + System.arraycopy( line, 0, buffer, len2, line.length ); + line = buffer; + } + + last = j; + } + + else if (ch == '\n' || ch == '\r') // Rule #4 + { + if ( ch == '\n' && i >= line.length - 1 && i >= 3 && line[i-3] != '=' ) + { + output[last++] = 10; + } + + else if ( ch == '\r' ) + { + if ( i < line.length && line[i-1] == '\r' && line[i] == '\n') + i++; + + output[last++] = 13; + } + +/* + for ( k = 0; k < nl.length; k++ ) + { + output[last++] = (byte) nl[k]; + + } +*/ + j = last; + } + + else // Rule #1, #2 + { + output[j++] = ch; + + if (ch != ' ' && ch != '\t') // Rule #3 + last = j; + } + +// if (j > output.length-5) +// output = Util.outputizeArray(output, output.length+500); + } +/*-------------------------------------------------------------- */ + + } + + param[0] = nNoOfLeftOverBytes; + param[1] = j; + buffer = null; + + return output; +} + + + + /** + * Base64 Decodes data from InputStream and writes to OutputStream. + * @param input InputStream that supplies the data to be decoded. + * @param output OutputStream that accepts the decoded data. + * @exception MIMEException If a decoding error occurs. + * return Number of decoded bytes + */ + protected static byte[] decodeBase64Vector( Vector v, int nStart, int nEnd, int nMessageLen, int param[] ) throws MIMEException + { + int add_bits; + int mask; + int out_byte = 0; + int out_bits = 0; + int byte_pos = 0; + int i, j, nLen = 0, nTotalLen = 0; + byte[] szLine; + byte[] output; + + if ( v == null || param == null ) + throw new MIMEException( szERROR_BAD_PARAMETER ); + + // restore previous state + out_byte = param[0]; + out_bits = param[1]; + + // invalid range + if ( nStart == -1 || nEnd == -1 ) + return null; + + // get message len + for ( j = nStart; j <= nEnd; j++ ) + { + szLine = (byte[]) v.elementAt( j ); + nTotalLen = nTotalLen + szLine.length; + } + + output = new byte[ nTotalLen ]; + + if ( output == null ) + throw new MIMEException( szERROR_OUT_OF_MEMORY ); + + // read off data from vector line by line + for ( j = nStart; j <= nEnd; j++ ) + { + szLine = (byte[]) v.elementAt( j ); + nLen = szLine.length; + + // process line + for ( i = 0; i < nLen && szLine[i] != 0; i++ ) + { + add_bits = Base64DecMap[(char) szLine[i]]; + + if (add_bits >= 64) + continue; + + out_byte = (out_byte << 6) + add_bits; + out_bits += 6; + + // If the queue has gotten big enough, put into the buffer + if (out_bits >= 24) + { + if ( byte_pos < nTotalLen - 3 ) + { + output[byte_pos++] = (byte) ( (out_byte & 0xFF0000) >> 16 ); + output[byte_pos++] = (byte) ( (out_byte & 0x00FF00) >> 8 ); + output[byte_pos++] = (byte) ( (out_byte & 0x0000FF) ); + out_bits = 0; + out_byte = 0; + } + else + { + System.out.println( "byte_pos = " + byte_pos ); + break; + } + } + } + } + + // save current state + param[0] = out_byte; + param[1] = out_bits; + param[2] = byte_pos; + + return output; + } + + + + protected static byte[] decodeBase64LeftOverBytes( int out_bits, int out_byte ) + { + int mask; + byte[] output = new byte[4]; + int byte_pos = 0; + + /* Handle any bits still in the queue */ + while (out_bits >= 8) + { + if (out_bits == 8) + { + output[byte_pos++] = (byte) out_byte; + out_byte = 0; + } + + else + { + mask = out_bits == 8 ? 0xFF : (0xFF << (out_bits - 8)); + output[byte_pos++] = (byte) ( (out_byte & mask) >> (out_bits - 8) ); + out_byte &= ~mask; + } + + out_bits -= 8; + } + + return byteSubstring( output, 0, byte_pos ); + } + + + + /** + * + * Base64 Decodes data from InputStream and writes to OutputStream + * @param input InputStream that supplies the data to be decoded. + * @param output OutputStream that accepts the decoded data. + * @exception MIMEException if a decoding error occurs. + * return number of decoded bytes + */ + public static void decodeBase64( InputStream input, OutputStream output ) throws MIMEException + { + int add_bits; + int mask; + int out_byte = 0; + int out_bits = 0; + int byte_pos = 0; + int i, nLen; + + if ( input == null || output == null ) + throw new MIMEException( szERROR_BAD_PARAMETER ); + + try + { + /* Queue up relevant bits */ + for ( i = 0, nLen = input.available(); i < nLen; i++ ) + { + add_bits = Base64DecMap[(char) input.read() ]; + + if (add_bits >= 64) + continue; + + out_byte = (out_byte << 6) + add_bits; + out_bits += 6; + + /* If the queue has gotten big enough, put into the buffer */ + if (out_bits == 24) + { + output.write( (byte) ( (out_byte & 0xFF0000) >> 16 ) ); + output.write( (byte) ( (out_byte & 0x00FF00) >> 8 ) ); + output.write( (byte) ( (out_byte & 0x0000FF) ) ); + + byte_pos =+ 3; + out_bits = 0; + out_byte = 0; + } + } + + /* Handle any bits still in the queue */ + while ( out_bits >= 8 ) + { + if (out_bits == 8) + { + output.write( (byte) out_byte ); + byte_pos++; + out_byte = 0; + } + + else + { + mask = out_bits == 8 ? 0xFF : (0xFF << (out_bits - 8)); + output.write( (byte) ( (out_byte & mask) >> (out_bits - 8) ) ); + byte_pos++; + out_byte &= ~mask; + } + + out_bits -= 8; + } + } + catch ( IOException e ) + { + throw new MIMEException ( e.getMessage() ); + } + + return; + } + + + + /* ------------------- utility functions ------------------ + * determine if both strings are the same based on the length of the second string + * case insensitive + * clee, oct 6,97 + * return TRUE if equale + */ + protected static boolean bStringEquals( String s1, String s2 ) + { + char ch; + + if ( s1 != null && s2 != null ) + { + int len = s2.length(); + + if ( len > s1.length() ) + return false; + + for ( int i = 0; i < len; i++ ) + { + ch = s2.charAt(i); + + // lowercase + if ( ch >= 97 ) + { + if ( s1.charAt(i) != ch && s1.charAt(i) != ( ch - 32 ) ) + return false; + } + else if ( s1.charAt(i) != ch && s1.charAt(i) != ( ch + 32 ) ) + return false; + } + + return true; + } + + return false; + } + + + /* ------------------- utility functions ------------------ + * determine if both strings are the same based on the length of the second string + * case insensitive + * clee, oct 6,97 + * return TRUE if equale + */ + protected static boolean bStringEquals( byte s1[], String s2 ) + { + char ch; + + if ( s1 != null && s2 != null ) + { + int len = s2.length(); + + if ( len > s1.length ) + return false; + + for ( int i = 0; i < len; i++ ) + { + ch = s2.charAt(i); + + // lowercase + if ( ch >= 97 ) + { + if ( s1[i] != ch && s1[i] != ( ch - 32 ) ) + return false; + } + else if ( s1[i] != ch && s1[i] != ( ch + 32 ) ) + return false; + } + + return true; + } + + return false; + } + + protected static boolean bStringEqualsLtrim( byte s1[], String s2 ) + { + char ch; + + if ( s1 != null && s2 != null ) + { + int j=0; + + int len = s2.length(); + + if ( len > s1.length ) + return false; + + for (j = 0; j < len; j++) + { + if (s1[j] == '\n' || s1[j] == '\r' || s1[j] == '\t' || s1[j] == ' '); + else + { + break; + } + } + + if (j > 0) + { + return (bStringEquals(j+1, s1, s2)); + } + + for ( int i = 0; i < len; i++ ) + { + ch = s2.charAt(i); + + // lowercase + if ( ch >= 97 ) + { + if ( s1[i] != ch && s1[i] != ( ch - 32 ) ) + return false; + } + else if ( s1[i] != ch && s1[i] != ( ch + 32 ) ) + return false; + } + + return true; + } + + return false; + } + + + /* ------------------- utility functions ------------------ + * determine if both strings are the same based on the length of the second string + * case insensitive + * clee, oct 6,97 + * return TRUE if equale + */ + protected static boolean bStringEquals( byte s1[], int s1_len, String s2 ) + { + char ch; + + if ( s1 != null && s2 != null ) + { + int len = s2.length(); + + if ( len > s1_len ) + return false; + + for ( int i = 0; i < len; i++ ) + { + ch = s2.charAt(i); + + // lowercase + if ( ch >= 97 ) + { + if ( s1[i] != ch && s1[i] != ( ch - 32 ) ) + return false; + } + else if ( s1[i] != ch && s1[i] != ( ch + 32 ) ) + return false; + } + + return true; + } + + return false; + } + + + /* ------------------- utility functions ------------------ + * determine if both strings are the same based on the length of the second string + * case insensitive + * clee, oct 6,97 + * return TRUE if equale + */ + protected static boolean bStringEquals( int offset, byte s1[], String s2 ) + { + char ch; + + if ( s1 != null && s2 != null ) + { + int len = s2.length(); + + if ( len > s1.length ) + return false; + + for ( int i = 0; i < len; i++ ) + { + ch = s2.charAt(i); + + // lowercase + if ( ch >= 97 ) + { + if ( s1[i+offset] != ch && s1[i+offset] != ( ch - 32 ) ) + return false; + } + else if ( s1[i+offset] != ch && s1[i+offset] != ( ch + 32 ) ) + return false; + } + + return true; + } + + return false; + } + + + /* ------------------- utility functions ------------------ + * determine if both strings are the same based on the length of the second string + * case insensitive + * clee, oct 6,97 + * return TRUE if equale + */ + protected static boolean bStringEquals( byte s1[], byte s2[] ) + { + char ch; + + if ( s2.length > s1.length ) + return false; + + if ( s1 != null && s2 != null ) + { + for ( int i = 0; i < s2.length; i++ ) + { + // lowercase + if ( s2[i] >= 97 ) + { + if ( s1[i] != s2[i] && s1[i] != ( s2[i] - 32 ) ) + return false; + } + else if ( s1[i] != s2[i] && s1[i] != ( s2[i] + 32 ) ) + return false; + } + + return true; + } + + return false; + } + + protected static byte[] stringToByte( String s ) { return s.getBytes(); } + + + protected static byte[] stringToByte( char[] s, int len ) + { + byte b[] = new byte[ len ]; + + for ( int i = 0; i < len; i++ ) + b[i] = (byte) s[i]; + + return b; + } + + protected static byte[] byteSubstring( byte[] s, int len ) + { + byte b[] = new byte[ len ]; + + for ( int i = 0; i < len; i++ ) + b[i] = s[i]; + + return b; + } + + protected static byte[] byteSubstring( byte[] s, int offset, int len ) + { + byte b[] = new byte[ len ]; + + for ( int i = 0, j = offset; i < len && j < s.length; i++, j++ ) + b[i] = s[j]; + + return b; + } + + +//---------------------------------------------------------------------------------------- + +} // End class diff --git a/msgsdk/java/protocol/MIME/src/MIMEMessage.java b/msgsdk/java/protocol/MIME/src/MIMEMessage.java new file mode 100644 index 00000000000..b9447db7a58 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEMessage.java @@ -0,0 +1,931 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.MIMEParser; +import netscape.messaging.mime.MIMEDataSink; + +/** + * The MIMEMessage class represents the MIME message. + * A MIME Message is made up of a set of headers and message content. + * The message content itself comprises one of the MIMEBody types: + * MIMEBasicPart, MIMEMultiPart, and MIMEMessagePart. + * @author Prasad Yendluri + */ +public class MIMEMessage implements Cloneable +{ + /** + * BodyPart is a MIMEBasicPart object. + */ + public static final int BASICPART = 0; + /** + * BodyPart is a MIMEMultiPart object. + */ + public static final int MULTIPART = 1; + /** + * BodyPart is a MIMEMessagePart object. + */ + public static final int MESSAGEPART = 2; + +//=========================================================================== +// +// Internal members not visible at the API +// +//=========================================================================== + + private static final int UNINITIALIZED = -1; + + private static final byte[] CRLF = "\r\n".getBytes(); + private static final byte[] LF = "\n".getBytes(); + + private int m_contentTransferEncoding; + protected int m_parsedPart; + private boolean m_fplaceTwo; + + // The key to the hashTable below is the name of the header field. + // To make sure we handle case differences in the header-name, we + // must convert the header name to lower-case always prior to the + // hash look-up. The entries added to the hash table are objects of + // class Header. + private Hashtable m_822HeadersTable; + private Vector m_repeatHdrs; + + // Body + private int m_bodyType; // one of BASICPART, MULTIPART, MESSAGEPART + private MIMEBodyPart m_theBody; // must be one of BASICPART, MULTIPART, MESSAGEPART + + // callback support + MIMEParser m_parser; + MIMEDataSink m_dataSink; + private Object m_UserObject; + + +//=========================================================================== +// +// CONSTRUCTORS +// +//=========================================================================== + + + /** + * Default constructor for the MIMEMessage class. + */ + public MIMEMessage () + { + m_822HeadersTable = new Hashtable(); + + m_parsedPart = 0; + m_contentTransferEncoding = UNINITIALIZED; + m_bodyType = UNINITIALIZED; + m_theBody = null; + m_parser = null; + m_dataSink = null; + + } + + /** + * Constructs a MIMEMessage object given a set of RFC822 headers. + * @param headers List of rfc822 headers to set in the message. + * @exception MIMEException If the headers are malformed. + */ + public MIMEMessage (Header[] headers) throws MIMEException + { + m_822HeadersTable = new Hashtable(); + + m_parsedPart = 0; + m_contentTransferEncoding = UNINITIALIZED; + m_bodyType = UNINITIALIZED; + m_theBody = null; + + if (headers != null) + for (int i = 0, len = headers.length; i < len; i++) + { + try + { + setHeader (headers[i].getName(), headers[i].getValue()); + } + catch (Exception e) + { + throw new MIMEException (e.getMessage()); + } + } // for + } + + + /** + * Constructs a (multi-part) MIMEMessage with given text and file. + * If filename parameter is null, builds a text message with textIS. Encoding will be E7BIT. + * If textIS parameter is null, builds a message with only the filename file as an attachment. + * If both textIS and filename parameters are null, an exception is thrown. + * @param textIS InputStream to text that becomes text part of the message. Can be null. + * @param filename Full name of file that becomes a part of the message. Can be null. + * @param encoding Encoding for the file attachment. To pick default value, pass -1. + * @see MIMEBodyPart#BASE64 + * @see MIMEBodyPart#QP + * @see MIMEBodyPart#BINARY + * @see MIMEBodyPart#E7BIT + * @see MIMEBodyPart#E8BIT + * @exception MIMEException If both textIS and filename are null or encoding is invalid + * @exception FileNotFoundException If filename file does not exist + * @exception SecurityException If filename file can not be accessed + * @exception IOExcepton On IO Errors on filename or textIS. + */ + public MIMEMessage (InputStream textIS, String filename, int encoding) throws + MIMEException, FileNotFoundException, SecurityException, IOException + { + MIMEBasicPart l_txtPart; + MIMEBasicPart l_filePart; + FileInputStream l_fis; + fileMIMEType l_fmt; + + // Message initialization + m_822HeadersTable = new Hashtable(); + m_parsedPart = 0; + m_contentTransferEncoding = UNINITIALIZED; + m_bodyType = UNINITIALIZED; + m_theBody = null; + + if (textIS == null && filename == null) + throw new MIMEException ("Invalid null filename:" + filename); + + if ((encoding != -1) && (encoding < MIMEBodyPart.BASE64 || encoding > MIMEBodyPart.E8BIT)) + throw new MIMEException ("Invalid MIME encoding: " + encoding); + + + if (textIS != null && filename != null) + { + MIMEMultiPart l_mmp = new MIMEMultiPart (textIS, filename, encoding); + this.setBody (l_mmp, false); + } + else if (textIS != null) // text-part only! + { + l_txtPart = new MIMEBasicPart (MIMEBasicPart.TEXT); + + l_txtPart.setContentSubType ("plain"); + l_txtPart.setContentTypeParams ("charset=us-ascii"); + l_txtPart.setContentEncoding(MIMEBodyPart.E7BIT); + //l_txtPart.setContentDisposition(MIMEBodyPart.INLINE); + + l_txtPart.setBodyData (textIS); + + this.setBody (l_txtPart, false); + } + else if (filename != null) // file-part only! + { + l_fis = new FileInputStream (filename); + + // The file-part + + l_fmt = MIMEHelper.getFileMIMEType (filename); + + if (l_fmt == null) + throw new MIMEException ("Can't determine MIME info for file: " + filename); + + l_filePart = new MIMEBasicPart (l_fmt.content_type); + + l_filePart.setContentSubType (l_fmt.content_subtype); + + if (l_fmt.content_params != null) + l_filePart.setContentTypeParams (l_fmt.content_params); + else + l_filePart.setContentTypeParams (new String ("name=" + filename)); + + if (encoding == -1) + l_filePart.setContentEncoding(l_fmt.mime_encoding); + else + l_filePart.setContentEncoding (encoding); + + if (l_fmt.content_type == MIMEBasicPart.TEXT); + //l_filePart.setContentDisposition (MIMEBodyPart.INLINE); + else + { + //l_filePart.setContentDisposition (MIMEBodyPart.ATTACHMENT); + l_filePart.setContentDispParams (new String ("filename=" + l_fmt.file_shortname)); + l_filePart.setContentDescription (l_fmt.file_shortname); + } + + // set body-data of this part + l_filePart.setBodyData (l_fis); + this.setBody (l_filePart, false); + + } // filePart + + } // MIMEMessage() + + + +//=========================================================================== +// +// All the Methods Specific to this Class +// +//=========================================================================== + + + /** + * Sets any RFC-822 headers including X-headers. Overwrites the existing + * value if header exists already. + * @param name Name of the header field. + * @param value Value of the header field to be added. + * @exception MIMEException If either name or value are NULL + * @see MIMEHelper#encodeHeader + */ + public void setHeader (String name, String value) throws MIMEException + { + if ((name == null) || (value == null)) + throw new MIMEException ("Invalid NULL Header name or value"); + + String l_hashKey = name.toLowerCase(); // Keys are always lower case + + Header l_hdr = new Header (name, value); + m_822HeadersTable.put (l_hashKey, l_hdr); + } + + + + /** + * Appends the value to an existing RFC822 header. Creates new header if + * one does not exist already. + * @param name Name of the header field. + * @param value Value of the header field to be added. + * @exception MIMEException If either name or value are NULL + * @see MIMEHelper#encodeHeader + */ + public void addHeader (String name, String value) throws MIMEException + { + if ((name == null) || (value == null)) + throw new MIMEException ("Invalid NULL Header name or value"); + + if (m_fplaceTwo == true) + { + addHeader2 (name, value); + return; + } + + String l_hashKey = name.toLowerCase(); // Keys are always lower case + Header l_hdr = (Header) m_822HeadersTable.get (l_hashKey); + if (l_hdr == null) + { + // Add a new one. + Header l_newhdr = new Header (name, value); + m_822HeadersTable.put (l_hashKey, l_newhdr); + } + else + { + // Append value to existing one. + StringBuffer l_oldvalue = new StringBuffer (l_hdr.getValue()); + l_oldvalue.append (value); + + Header l_newhdr = new Header (name, l_oldvalue.toString()); + m_822HeadersTable.put (l_hashKey, l_newhdr); + } + } + + private void addHeader2 (String name, String value) throws MIMEException + { + Header l_oldHdr, l_newhdr; + int idx = m_repeatHdrs.size() -1; + + l_oldHdr = (Header) m_repeatHdrs.elementAt (idx); + + if (!(l_oldHdr.m_headerName.equalsIgnoreCase (name))) + { + l_newhdr = new Header (name, value); + m_repeatHdrs.addElement (l_newhdr); + m_fplaceTwo = true; + } + else + { + m_repeatHdrs.removeElementAt (idx); + + StringBuffer l_oldvalue = new StringBuffer (l_oldHdr.getValue()); + l_oldvalue.append (value); + + l_newhdr = new Header (name, l_oldvalue.toString()); + m_repeatHdrs.addElement (l_newhdr); + m_fplaceTwo = true; + } + } + + // Adds a potential repeat header to the message + protected void addRHeader (String name, String value) throws MIMEException + { + if ((name == null) || (value == null)) + throw new MIMEException ("Invalid NULL Header name or value"); + + String l_hashKey = name.toLowerCase(); // Keys are always lower case + Header l_hdr = (Header) m_822HeadersTable.get (l_hashKey); + + if (l_hdr == null) + { + // Add a new one. + Header l_newhdr = new Header (name, value); + m_822HeadersTable.put (l_hashKey, l_newhdr); + m_fplaceTwo = false; + } + else + { + // Its a repeat header! + if (m_repeatHdrs == null) + m_repeatHdrs = new Vector(); + + Header l_newhdr = new Header (name, value); + m_repeatHdrs.addElement (l_newhdr); + m_fplaceTwo = true; + } + } + + /** + * Returns the value of the requested header. NULL if the header is not present. + * @param name Name of the header field. + * @exception MIMEException If name is NULL + * @see MIMEHelper#decodeHeader + */ + public String getHeader (String name) throws MIMEException + { + if (name == null) + throw new MIMEException ("Invalid NULL Header name"); + + String l_hashKey = name.toLowerCase(); // Keys are always lower case + Header l_hdr = (Header) m_822HeadersTable.get (l_hashKey); + + if (l_hdr != null) + return (l_hdr.getValue()); + else + return (null); + } + + /** + * Deletes the requested header. Ignores if the header specified does not exist. + * @param name Name of the header field to delete. + * @exception MIMEException if name is null + */ + public void deleteHeader (String name) throws MIMEException + { + if (name == null) + throw new MIMEException ("Invalid NULL Header name"); + + String l_hashKey = name.toLowerCase(); // Keys are always lower case + m_822HeadersTable.remove (l_hashKey); + } + + /** + * Returns all the RFC-822 headers in the Message as an array of Header objects. + * Content-Type is not returned by this Method, as separate methods to get + * Content primary type, sub-type and parameters exist. + * @exception MIMEException If no headers exist. + * @see MIMEHelper#decodeHeader + * @see getContentType + * @see getContentSubType + * @see getContentTypeParams + */ + public Header[] getAllHeaders () throws MIMEException + { + int l_numElements = m_822HeadersTable.size(); + int i = 0, l_repeats = 0; + Header[] hdrs = null; + + if (l_numElements <= 0) + return (null); + + if (m_repeatHdrs != null) + l_repeats = m_repeatHdrs.size(); + + hdrs = new Header[l_numElements + l_repeats]; + Enumeration he = m_822HeadersTable.elements(); + + while (he.hasMoreElements()) + { + hdrs[i++] = (Header) he.nextElement(); + } + + for (int j = 0; j < l_repeats; j++) + hdrs[i++] = (Header) m_repeatHdrs.elementAt (j); + + return (hdrs); + } + + /** + * Returns the type of the body of the Message. + * @exception MIMEException If no Body exists for the message. + * @see MIMEMessage#BASICPART + * @see MIMEMessage#MULTIPART + * @see MIMEMessage#MESSAGEPART + */ + public int getBodyType () throws MIMEException + { + if (m_bodyType != UNINITIALIZED) + return (m_bodyType); + else + throw new MIMEException ("getBodyType(): No body present!"); + } + + /** + * Returns the content-type of the Message. + * The content-type returned is the MIME content-type. + * @exception MIMEException If no Body exists. + */ + public String getContentType () throws MIMEException + { + if (m_theBody == null) + throw new MIMEException ("getContentType(): No body present!"); + + return (m_theBody.getContentType()); + + +/* if (m_theBody instanceof MIMEBasicPart) + * { + * MIMEBasicPart l_body = (MIMEBasicPart) m_theBody; + * return (l_body.getContentType()); + * } + * else if (m_theBody instanceof MIMEMessagePart) + * { + * MIMEMessagePart l_body = (MIMEMessagePart) m_theBody; + * return (l_body.getContentType()); + * } + * else if (m_theBody instanceof MIMEMultiPart) + * { + * MIMEMultiPart l_body = (MIMEMultiPart) m_theBody; + * return (l_body.getContentType()); + * } + * else + * throw new MIMEException ("getContentType(): Invalid body!"); + */ + } + + /** + * Returns the content subtype of the Message. NULL if none exists. + * @exception MIMEException If no Body exists. + */ + public String getContentSubType () throws MIMEException + { + if (m_theBody == null) + throw new MIMEException ("getContentSubType(): No body present!"); + + return (m_theBody.getContentSubType()); + +/* MIMEBodyPart l_body = (MIMEBodyPart) m_theBody; + * return (l_body.getContentSubType()); + * + * if (m_theBody instanceof MIMEBasicPart) + * { + * MIMEBasicPart l_body = (MIMEBasicPart) m_theBody; + * return (l_body.getContentSubType()); + * } + * else if (m_theBody instanceof MIMEMessagePart) + * { + * MIMEMessagePart l_body = (MIMEMessagePart) m_theBody; + * return (l_body.getContentSubType()); + * } + * else if (m_theBody instanceof MIMEMultiPart) + * { + * MIMEMultiPart l_body = (MIMEMultiPart) m_theBody; + * return (l_body.getContentSubType()); + * } + * else + * throw new MIMEException ("getContentSubType(): Invalid body!"); + */ + } + + /** + * Returns the content-type params of the Message. NULL if none exist. + * @exception MIMEException If no Body exists. + */ + public String getContentTypeParams () throws MIMEException + { + if (m_theBody == null) + throw new MIMEException ("getContentTypeParams(): No body present!"); + + return (m_theBody.getContentTypeParams()); + +/* MIMEBodyPart l_body = (MIMEBodyPart) m_theBody; + * return (l_body.getContentTypeParams()); + * + * if (m_theBody instanceof MIMEBasicPart) + * { + * MIMEBasicPart l_body = (MIMEBasicPart) m_theBody; + * return (l_body.getContentTypeParams()); + * } + * else if (m_theBody instanceof MIMEMessagePart) + * { + * MIMEMessagePart l_body = (MIMEMessagePart) m_theBody; + * return (l_body.getContentTypeParams()); + * } + * else if (m_theBody instanceof MIMEMultiPart) + * { + * MIMEMultiPart l_body = (MIMEMultiPart) m_theBody; + * return (l_body.getContentTypeParams()); + * } + * else + * throw new MIMEException ("getContentTypeParams(): Invalid body!"); + */ + } + + + /** + * Returns an Object of corresponding MIMEBodyPart type that is + * the body of this Message. + * @param clone Whether to return a reference to the internal object or a cloned copy; + * if true: cloned copy; if false: reference to the object. + * @exception MIMEException If no Body exists. + * @see MIMEBasicPart + * @see MIMEMultiPart + * @see MIMEMessagePart + */ + public Object getBody (boolean clone) throws MIMEException + { + if (m_theBody == null) + throw new MIMEException ("getBody(): No body present!"); + + if (m_theBody instanceof MIMEBasicPart) + { + try + { + MIMEBasicPart l_body = (MIMEBasicPart) m_theBody; + return ( clone ? l_body.clone() : l_body ); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else if (m_theBody instanceof MIMEMessagePart) + { + try + { + MIMEMessagePart l_body = (MIMEMessagePart) m_theBody; + return ( clone ? l_body.clone() : l_body ); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else if (m_theBody instanceof MIMEMultiPart) + { + try + { + MIMEMultiPart l_body = (MIMEMultiPart) m_theBody; + return ( clone ? l_body.clone() : l_body ); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else + throw new MIMEException ("getBody(): Invalid body!"); + } + + + + /** + * Returns an Object of corresponding MIME BodyPart type that is + * the body of this Message. + * @exception MIMEException If no Body exists. + */ + protected Object getBody () throws MIMEException + { + return getBody( true ); + } + + + /** + * Deletes the body of the Message. Has no effect if no body is present. + */ + public void deleteBody () + { + if (m_theBody != null) + { + m_theBody = null; + m_bodyType = UNINITIALIZED; + } + } + + + /** + * Sets the MIMEBasicPart as Body of this Message. This part must already be + * constructed and fully set-up. Sets the content-type of the message based on the + * type of the part added by this method. + * @param part BodyPart to add. + * @exception MIMEException If body is already set (or) part is null. + * @see MIMEBasicPart + */ + protected void setBody (MIMEBasicPart part ) throws MIMEException + { + setBody( part, true ); + } + + + + /** + * Sets the MIMEBodyPart as Body of this Message. This part must already be + * constructed and fully set-up. Sets the content-type of the message based on the + * type of the part being added. This MIMEBodyPart must be an object of one of the + * three concrete classes: MIMEBasicPart, MIMEMultiPart and MIMEMessagePart. + * @param part BodyPart to add. + * @param clone Whether to store a reference to the object or a cloned copy; + * if true: clones a copy; if false: stores a reference to the object. + * @exception MIMEException If body is already set, or if part is null, or + * if part is not an object of one of the three concrete classes: + * MIMEBasicPart, MIMEMultiPart, or MIMEMessagePart. + * @see MIMEBasicPart + * @see MIMEMultiPart + * @see MIMEMessagePart + */ + public void setBody (MIMEBodyPart part, boolean clone) throws MIMEException + { + if (part == null) + throw new MIMEException ("setBody(): null part passed!"); + + if (part instanceof MIMEBasicPart) + setBody ((MIMEBasicPart) part, clone); + else if (part instanceof MIMEMessagePart) + setBody ((MIMEMessagePart) part, clone); + else if (part instanceof MIMEMultiPart) + setBody ((MIMEMultiPart) part, clone); + else + throw new MIMEException ("setBody(): Invalid part "); + } + + /** + * Sets the MIMEBasicPart as Body of this Message. This part must already be + * constructed and fully set-up. Sets the content-type of the message based on the + * type of the part being added. + * @param part BodyPart to add. + * @param clone If false stores reference to passed object instead of cloning a copy. + * @exception MIMEException If body is already set (or) part is null. + * @see MIMEBasicPart + */ + protected void setBody (MIMEBasicPart part, boolean clone) throws MIMEException + { + MIMEBasicPart l_part = null; + + if (m_theBody != null) + throw new MIMEException ("setBody(): Body already set!"); + + if (part == null) + throw new MIMEException ("setBody(): null part passed!"); + + try + { + l_part = (MIMEBasicPart) ( clone ? part.clone() : part ); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + + m_theBody = l_part; + m_bodyType = BASICPART; + } + + /** + * Sets the MIMEMultiPart as Body of this Message. This part must already be + * constructed and fully set-up. Sets the content-type of the message based on the + * type of the part being added + * @param part BodyPart to add. + * @exception MIMEException If body is already set (or) part is null. + * @see MIMEBasicPart + */ + protected void setBody (MIMEMultiPart part) throws MIMEException + { + setBody( part, true ); + } + + + /** + * Sets the MIMEMultiPart as Body of this Message. This part should have been + * constructed and fully set-up. Sets the content-type of the message accordingly. + * @param part BodyPart to add. + * @param clone If false stores reference to passed object instead of cloning a copy. + * @exception MIMEException If body is already set (or) part is null. + * @see MIMEMultiPart + */ + protected void setBody (MIMEMultiPart part, boolean clone) throws MIMEException + { + MIMEMultiPart l_part = null; + + if (m_theBody != null) + throw new MIMEException ("setBody(): Body already set!"); + + if (part == null) + throw new MIMEException ("setBody(): null part passed!"); + + try + { + l_part = (MIMEMultiPart) ( clone ? part.clone() : part ); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + + m_theBody = l_part; + m_bodyType = MULTIPART; + } + + + /** + * Sets the MIMEMessagePart as Body of this Message. This part must already be + * constructed and fully set-up. Sets the content-type of the message based on the + * type of the part being added. + * @param part BodyPart to add. + * @exception MIMEException If body is already set (or) part is null. + * @see MIMEBasicPart + */ + protected void setBody (MIMEMessagePart part) throws MIMEException + { + setBody( part, true ); + } + + + /** + * Sets the MIMEMessagePart as Body of this Message. This part must already be + * constructed and fully set-up. Sets the content-type of the message accordingly. + * @param part BodyPart to add. + * @param clone If false stores reference to passed object instead of cloning a copy. + * @exception MIMEException If body is already set (or) part is null. + * @see MIMEMessagePart + */ + protected void setBody (MIMEMessagePart part, boolean clone) throws MIMEException + { + MIMEMessagePart l_part = null; + + if (m_theBody != null) + throw new MIMEException ("setBody(): Body already set!"); + + if (part == null) + throw new MIMEException ("setBody(): null part passed!"); + + try + { + l_part = (MIMEMessagePart) ( clone ? part.clone() : part ); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + + m_theBody = l_part; + m_bodyType = MESSAGEPART; + } + + + /** + * Outputs a byte-stream for this Message in MIME format with transfer encoding + * applied to all bodyParts as applicable. + * @param fullfilename Filename, including full path to write the byte-stream to. + * @exception IOException If an IO error occurs. + * @exception MIMEException If any required fields in the bodyPart are not set-up. + */ + public void putByteStream (String fullfilename) throws IOException, MIMEException + { + FileOutputStream fos; + fos = new FileOutputStream (fullfilename); + putByteStream (fos); + } + + + + /** + * Outputs a byte stream for this Message in MIME format with transfer encoding + * applied to all bodyParts as applicable. + * @param os OutputStream to write to. + * @exception IOException If an IO error occurs. + * @exception MIMEException If detects an error during encoding. + */ + public void putByteStream (OutputStream os) throws IOException, MIMEException + { + // Don't write content-type here. The Body will do that. That is, if it were + // a multi-part, it would do that. If it were a basic-part, it would do that etc. + + if (m_theBody == null) + { + throw new MIMEException ("MIMEMessage.putByteStream(). No body!"); + } + + // write out all the headers. + //StringBuffer l_hdrbuf; + boolean l_fMIMEVersionWritten = false; + byte[] l_bytebuf; + + Header[] hdrs = getAllHeaders (); + + if (hdrs != null) + for (int i = 0, len = hdrs.length; i < len; i++) + { + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + if (l_fMIMEVersionWritten == false && + (hdrs[i].m_headerName).equalsIgnoreCase("MIME-Version")) + l_fMIMEVersionWritten = true; + l_bytebuf = MIMEHelper.unicodeToASCII (hdrs[i].getLine()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException (hdrs[i].getLine() + "> " + e.getMessage()); + } + } // for + + // Now put the MIME Version. + if (!l_fMIMEVersionWritten && m_parsedPart==0) + { + l_bytebuf = MIMEHelper.unicodeToASCII ("MIME-Version: 1.0\r\n"); + os.write (l_bytebuf); + } + + // call thebody.putByteStream() + if (m_theBody instanceof MIMEBasicPart) + { + MIMEBasicPart l_body = (MIMEBasicPart) m_theBody; + l_body.putByteStream(os); + } + else if (m_theBody instanceof MIMEMessagePart) + { + MIMEMessagePart l_body = (MIMEMessagePart) m_theBody; + l_body.putByteStream(os); + } + else if (m_theBody instanceof MIMEMultiPart) + { + MIMEMultiPart l_body = (MIMEMultiPart) m_theBody; + l_body.putByteStream(os, true); // needPreamble! + } + else + throw new MIMEException ("putByteStream(): Invalid body!"); + + //os.write (CRLF); // terminate the message + + } // end putByteStream() + + + /** + * Clones an instance of this MIMEMessage object. + * @exception CloneNotSupportedException If thrown by constituent components. + */ + public Object clone () throws CloneNotSupportedException + { + MIMEMessage l_theClone = (MIMEMessage) super.clone(); + + if (m_822HeadersTable != null) + l_theClone.m_822HeadersTable = (Hashtable) m_822HeadersTable.clone(); + + if (m_repeatHdrs != null) + l_theClone.m_repeatHdrs = (Vector) m_repeatHdrs.clone(); + + if (m_theBody != null) + { + if (m_theBody instanceof MIMEBasicPart) + { + MIMEBasicPart l_body = (MIMEBasicPart) m_theBody; + l_theClone.m_theBody = (MIMEBasicPart) l_body.clone(); + } + else if (m_theBody instanceof MIMEMessagePart) + { + MIMEMessagePart l_body = (MIMEMessagePart) m_theBody; + l_theClone.m_theBody = (MIMEMessagePart) l_body.clone(); + } + else if (m_theBody instanceof MIMEMultiPart) + { + MIMEMultiPart l_body = (MIMEMultiPart) m_theBody; + l_theClone.m_theBody = (MIMEMultiPart) l_body.clone(); + } + else + return (null); + + l_theClone.m_UserObject = m_UserObject; + + } // end if + + return (l_theClone); + } + + protected Object getUserObject() { return m_UserObject; } + protected void setUserObject( Object userObject ) { m_UserObject = userObject; } +} diff --git a/msgsdk/java/protocol/MIME/src/MIMEMessagePart.java b/msgsdk/java/protocol/MIME/src/MIMEMessagePart.java new file mode 100644 index 00000000000..4333c3b47f7 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEMessagePart.java @@ -0,0 +1,659 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + + +package netscape.messaging.mime; +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.MIMEParser; + + + +/** + * The MIMEMessagePart class implements the MIME MessagePart Content Type. + * @author Prasad Yendluri + */ +public class MIMEMessagePart extends MIMEBodyPart implements Cloneable +{ + +//=========================================================================== +// +// Internal members not visible at the API +// +//=========================================================================== + + // Encodings + private static final int BINARY = MIMEBodyPart.BINARY; + private static final int E7BIT = MIMEBodyPart.E7BIT; + private static final int E8BIT = MIMEBodyPart.E8BIT; + + + private static final int UNINITIALIZED = -1; + + private static final String[] m_stringDisposition = { "Attachment", "Inline"}; + private static final String[] m_stringEncoding = { "base64", "quoted-printable", + "binary", "7bit", "8bit" }; + private static final String MESSAGE = "Message"; + + private int m_contentTransferEncoding; + private int m_parsedPart; + + // The key to the hashTable below is the name of the header field. + // To make sure we handle case differences in the header-name, we + // must convert the header name to lower-case always prior to the + // hash look-up. The entries added to the hash table are objects of + // class Header. + private Hashtable m_ExtBodyHeadersTable; + + private MIMEMessage m_theMessage; + private InputStream m_partial; + + +//=========================================================================== +// +// CONSTRUCTORS +// +//=========================================================================== + + /** + * Constructs a MIMEMessagePart object. + * Default constructor for the MIMEMessagePart. + */ + public MIMEMessagePart () + { + m_parsedPart = 0; + m_UserObject = null; + m_contentDisposition = UNINITIALIZED; + m_contentTransferEncoding = UNINITIALIZED; + m_theMessage = null; + m_partial = null; + m_ExtBodyHeadersTable = null; + } + + + + /** + * Constructs a MIMEMessagePart object from the given MIMEMessage Object. + * @param msg The message that should form the body of this part. + * @exception MIMEException If msg is null. + */ + public MIMEMessagePart (MIMEMessage msg) throws MIMEException + { + m_parsedPart = 0; + m_contentDisposition = UNINITIALIZED; + m_contentTransferEncoding = UNINITIALIZED; + + if (msg != null) + { + try + { + m_theMessage = (MIMEMessage) msg.clone(); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else + throw new MIMEException ("MIMEMessagePart(): null message passed." ); + + } + +//=========================================================================== +// +// Methods from Interface MIMEBodyPart +// +//=========================================================================== + + /** + * Returns Content-Type of this MIME Part. Always returns the string "Message". + */ + public String getContentType () + { + return (MESSAGE); + } + + + /** + * Sets Content-Transfer-Encoding of this MIME Part. + * Only BINARY, E7BIT, and E8BIT are applicable to MIMEMessagePart. + * Additionally, for message/partial and message/external-body, the MIME + * standard requires E7BIT encoding. It is possible to + * set encoding prior to setting the contentSubType. + * If a different value is specified, no error occurs; instead, + * this method will be overridden by the putByteStream() method. + * @param encoding Value that represents the encoding. + * @see MIMEBodyPart#BINARY + * @see MIMEBodyPart#E7BIT + * @see MIMEBodyPart#E8BIT + * @exception MIMEException If the value is not one of BINARY, E7BIT or E8BIT + */ + public void setContentEncoding (int encoding) throws MIMEException + { + switch (encoding) + { + case BINARY: + case E7BIT: + case E8BIT: + m_contentTransferEncoding = encoding; + break; + default: + throw new MIMEException ("Invalid/Inapplicable Content-Transfer-Encoding: " + encoding); + } + } + + /** + * Returns value of Content-Transfer-Encoding of this MIME Part. -1 if none present. + * @see MIMEBodyPart#BINARY + * @see MIMEBodyPart#E7BIT + * @see MIMEBodyPart#E8BIT + */ + public int getContentEncoding () + { + if (m_contentTransferEncoding == UNINITIALIZED) + return (-1); + + return (m_contentTransferEncoding); + } + +//=========================================================================== +// +// Methods Specific to this Class alone. +// +//=========================================================================== + + + /** + * Sets the specified message as the body of this part. + * @param msg Message to be set as body of this part. + * @exception MIMEException If already set (or) can not be set. + */ + protected void setMessage (MIMEMessage msg) throws MIMEException + { + setMessage (msg, true); + } + + + /** + * Sets the specified message as the body of this part. + * @param msg Message to be set as body of this part. + * @param clone If false stores reference to passed object instead of cloning a copy. + * @exception MIMEException If already set (or) can not be set. + */ + public void setMessage (MIMEMessage msg, boolean clone) throws MIMEException + { + if (m_theMessage != null) + throw new MIMEException ("setMessage(): already set!"); + + if (msg != null) + { + try + { + m_theMessage = (MIMEMessage) (clone ? msg.clone() : msg); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else + throw new MIMEException ("setMessage(): null message passed." ); + } + + /** + * Returns the MIMEMessage that is the Body of this part. + * @exception MIMEException If no Message exists in body-data. + */ + protected MIMEMessage getMessage () throws MIMEException + { + return getMessage (true); + } + + + /** + * Returns the MIMEMessage that is the Body of this part. + * @param clone If false returns reference to the object instead of a cloned copy. + * @exception MIMEException If no Message exists in body-data. + */ + public MIMEMessage getMessage (boolean clone) throws MIMEException + { + if (m_theMessage == null) + throw new MIMEException ("getMessage(): null Message in body!"); + + try + { + MIMEMessage l_msg = (MIMEMessage) (clone ? m_theMessage.clone() : m_theMessage); + return (l_msg); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + + /** + * Deletes the MIMEMessage that is the Body of this part. + * Noop if no body was ever set. + */ + public void deleteMessage () + { + m_theMessage = null; + } + + /** + * Sets the headers associated with External Body, for external-body content-subtypes + * only. If already set, these headers are + * ignored for other subtypes (rfc822 and message-partial). + * @param name Name of the header field. Should not include ':' + * @param value Value of the header field to be added. + * @exception MIMEException if either of name or value is null + */ + public void setExtBodyHeader (String name, String value) throws MIMEException + { + if ((name == null) || (value == null)) + throw new MIMEException ("Invalid NULL Header name or value"); + + if ( name.indexOf (':') > -1) + throw new MIMEException ("Invalid ':' in Header name"); + + if (m_ExtBodyHeadersTable == null) + m_ExtBodyHeadersTable = new Hashtable(); + + String l_hashKey = name.toLowerCase(); // Keys are always lower case + Header l_hdr = new Header (name, value); + m_ExtBodyHeadersTable.put (l_hashKey, l_hdr); + } + + /** + * Returns the value of the specified header associated with External Body. + * Applicable to external-body subtypes only. + * Returns NULL if the header is not present. + * @param name Name of the header field. + * @exception MIMEException If name passed is a NULL. + */ + public String getExtBodyHeader (String name) throws MIMEException + { + if (name == null) + throw new MIMEException ("Invalid NULL Header name"); + + if (m_ExtBodyHeadersTable == null) + return (null); + + String l_hashKey = name.toLowerCase(); // Keys are always lower case + Header l_hdr = (Header) m_ExtBodyHeadersTable.get (l_hashKey); + + if (l_hdr != null) + return (l_hdr.getValue()); // getValue() returns a new string. + else + return (null); + } + + + /** + * Returns all the headers in this Part as an array of Header objects. + * @exception MIMEException If no headers exist. + */ + public Header[] getAllHeaders () throws MIMEException + { + int l_numElements = 0, i = 0; + + if (m_ExtBodyHeadersTable != null) + l_numElements = m_ExtBodyHeadersTable.size(); + + if (l_numElements <= 0) + return (null); + + Header[] hdrs = new Header [l_numElements]; + Enumeration he = m_ExtBodyHeadersTable.elements(); + + while (he.hasMoreElements()) + { + hdrs[i++] = (Header) he.nextElement(); + } + + return (hdrs); + } + + + /** + * Sets the message partial. Stores reference to the supplied inputStream. + * Does not copy data. The data in partial is copied as is by putByteStream(). + * To be used with message/partial subtypes only. It is an error to setPartial() + * for other subtypes (rfc822 and external-body). + * @param instream inputStream that supplies the data for this partial. + * @exception MIMEException if content-subtype is not partial or if already set. + * @see deletePartial + */ + protected void setPartial (InputStream instream) throws MIMEException + { + if (m_partial != null) + throw new MIMEException ("Partial already set."); + + if ((m_contentSubType != null) && + (!m_contentSubType.equalsIgnoreCase("partial"))) + throw new MIMEException ("Content-Subtype partial expected."); + + m_partial = instream; + + } + + /** + * Deletes the message partial data of this part. Ignores if partial was not set. + * @see setPartial + */ + protected void deletePartial () + { + m_partial = null; + } + + /** + * Outputs a byte-stream for this part with its MIME part headers and encoded + * body data. + * @param os OutputStream to write to. + * @exception IOException If an IO error occurs. + * @exception MIMEException If encoding error is detected. + */ + public void putByteStream (OutputStream os) throws IOException, MIMEException + { + boolean l_external_body = false; + boolean l_message_partial = false; + String l_contentSubType = null; + if (m_contentSubType != null) + l_contentSubType = m_contentSubType.trim(); + + if ((l_contentSubType != null) && + (l_contentSubType.equalsIgnoreCase("external-body"))) + { + l_external_body = true; + + if (m_ExtBodyHeadersTable == null); + //throw new MIMEException ("External body header Content-Id required for external-body"); + } + + if ((l_contentSubType != null) && + (l_contentSubType.equalsIgnoreCase("partial"))) + { + l_message_partial = true; + + if (m_partial == null) + throw new MIMEException ("Message partial not set"); + + if (m_contentTransferEncoding != E7BIT && m_contentTransferEncoding != UNINITIALIZED) + throw new MIMEException ("Bad content-encoding for message/partial"); + + } + + if ((m_theMessage == null) && (!l_external_body) && (!l_message_partial)) + { + throw new MIMEException ("putByteStream(): null Message body!"); + } + + // Write out the headers first + StringBuffer l_hdrbuf = new StringBuffer (HDRBUFSZ); + byte[] l_bytebuf; + + // content-type + l_hdrbuf.append ("Content-Type: message/"); + + if (m_contentSubType != null) + l_hdrbuf.append (m_contentSubType); + else + { + if (m_theMessage != null) + l_hdrbuf.append ("rfc822"); // default + else + throw new MIMEException ("putByteStream(): No content-subtype." ); + } + + if (m_contentTypeParams != null && m_contentTypeParams.length() > 0) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentTypeParams); + } + + l_hdrbuf.append ("\r\n"); + + // write the header out to os + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Type: " + e.getMessage()); + } + + // contentID + if (m_contentID != null) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-ID: " + m_contentID); + l_hdrbuf.append ("\r\n"); + + try + { + // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-ID: " + e.getMessage()); + } + } + + // contentDisposition + if (m_contentDisposition != UNINITIALIZED) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Disposition: " + + m_stringDisposition [m_contentDisposition]); + + if (m_contentDispParams != null) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentDispParams); + } + + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Disposition: " + e.getMessage()); + } + } + + // contentDescription + if (m_contentDescription != null) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Description: " + m_contentDescription); + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Description: " + e.getMessage()); + } + } + + // Content-transfer-encoding + //if (m_contentTransferEncoding == UNINITIALIZED) + //{ + // m_contentTransferEncoding = E7BIT; // default + //} + + if (m_contentSubType != null) + { + if ((l_message_partial || l_external_body) && m_contentTransferEncoding != UNINITIALIZED) + m_contentTransferEncoding = E7BIT; // As mandated by MIME standard + } + + //if (!l_external_body) // for external-body we write it later + { + if (m_contentTransferEncoding != UNINITIALIZED) + { + if (l_external_body) + m_contentTransferEncoding = E7BIT; + + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Transfer-Encoding: " + + m_stringEncoding [m_contentTransferEncoding]); + l_hdrbuf.append ("\r\n"); + + try + { + // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Transfer-Encoding: " + e.getMessage()); + } + } + } + + + // blank-line after headers + //l_bytebuf = new String("\r\n").getBytes("iso-8859-1"); + os.write (CRLF); + + // write out the message part if it is not external-body + if ((m_theMessage != null) && (!l_external_body) && (!l_message_partial)) + m_theMessage.putByteStream(os); + else if (l_external_body) + { + //os.write (CRLF); // Prior to the headers for the external-body + + if (m_ExtBodyHeadersTable != null) + { + Enumeration he = m_ExtBodyHeadersTable.elements(); + + while (he.hasMoreElements()) + { + Header l_tmphdr = (Header) he.nextElement(); + + try + { + l_bytebuf = MIMEHelper.unicodeToASCII (l_tmphdr.getLine()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException (l_tmphdr.getLine() + e.getMessage()); + } + + } // while + + //if (m_contentTransferEncoding != UNINITIALIZED) + //{ + // // write out content-encoding + // l_hdrbuf.setLength (0); + // l_hdrbuf.append ("Content-Transfer-Encoding: " + + // m_stringEncoding [m_contentTransferEncoding]); + // l_hdrbuf.append ("\r\n"); + // + // try + // { + // // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + // l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + // os.write (l_bytebuf); + // } + // catch (Exception e) + // { + // throw new MIMEException ("Content-Transfer-Encoding: " + e.getMessage()); + // } + // } + + os.write (CRLF); // After the headers for the external-body + + } // if m_ExtBodyHeadersTable + + } // if l_external_body + else if (l_message_partial) + { + os.write (CRLF); + byte l_buf [] = new byte [DATABUFSZ]; + int l_len; + + while ( (l_len = m_partial.read (l_buf, 0, DATABUFSZ)) > 0) + { + os.write (l_buf, 0, l_len); + } + + } // if l_external_body + + os.write (CRLF); // terminate the part + + } // end putByteStream() + + /** + * Clones an instance of this MIMEMessagePart object. + * @exception CloneNotSupportedException If thrown by a constituent components. + */ + public Object clone () throws CloneNotSupportedException + { + MIMEMessagePart l_theClone = (MIMEMessagePart) super.clone(); + + if (m_theMessage != null) + { + l_theClone.m_theMessage = (MIMEMessage) m_theMessage.clone(); + } + + if (m_contentSubType != null) + l_theClone.m_contentSubType = m_contentSubType; + + if (m_contentTypeParams != null) + l_theClone.m_contentTypeParams = m_contentTypeParams; + + if (m_contentID != null) + l_theClone.m_contentID = m_contentID; + + if (m_contentDispParams != null) + l_theClone.m_contentDispParams = m_contentDispParams; + + if (m_contentDescription != null) + l_theClone.m_contentDescription = m_contentDescription; + + l_theClone.m_UserObject = m_UserObject; + + return (l_theClone); + } +} diff --git a/msgsdk/java/protocol/MIME/src/MIMEMultiPart.java b/msgsdk/java/protocol/MIME/src/MIMEMultiPart.java new file mode 100644 index 00000000000..8287828a616 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEMultiPart.java @@ -0,0 +1,911 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.MIMEParser; + +/** + * The MIMEMultiPart class implements the MIME multi-part Content Type. + *

The multi-part content type represents messages with multiple + * attachments of potentially different media. This content type + * describes a message that is made up of one or more sub-body parts. + * The Multipart type has several subtypes that describe how the + * sub-parts relate to each other. These include mixed, alternative, + * digest, and parallel. + * @author Prasad Yendluri + */ +public class MIMEMultiPart extends MIMEBodyPart implements Cloneable +{ + +//=========================================================================== +// +// Internal members not visible at the API +// +//=========================================================================== + + private static final int UNINITIALIZED = -1; + private static final String[] m_stringDisposition = { "Attachment", "Inline"}; + private static final String[] m_stringEncoding = { "base64", "quoted-printable", + "binary", "7bit", "8bit" }; + private static final String MULTIPART = "MultiPart"; + + //private int m_contentType; + //private int m_contentTransferEncoding; + protected int m_parsedPart; + private int m_bpCount; // count of bodyparts + private boolean m_needPreamble; + protected boolean m_endPart; + + // Vector to hold the bodyparts. + private Vector m_bodyParts; + private String m_boundary; + private String m_preamble; + private int m_contentTransferEncoding; + + +//=========================================================================== +// +// CONSTRUCTORS +// +//=========================================================================== + + /** + * Constructs a MIMEMultiPart object. + * Default constructor for the MIMEMultiPart + */ + public MIMEMultiPart () + { + m_parsedPart = 0; + m_contentDisposition = UNINITIALIZED; + m_contentTransferEncoding = UNINITIALIZED; + m_needPreamble = false; + m_bodyParts = new Vector(); + m_boundary = null; + m_UserObject = null; + } + + + /** + * Constructs a MIMEMultiPart object with the specified text and filename. + * Both textIS and filename cannot be null or an exception is thrown. + * (See MIMEMessage() if you need to do this.) + * @param textIS InputStream to text that becomes (first) text part of the multi-part. + * @param filename Name of the file that becomes second part of the multi-part. + * @param encoding Encoding for the file attachment. To select a default encoding, use -1. + * @see #MIMEMessage + * @see MIMEBodyPart#BASE64 + * @see MIMEBodyPart#QP + * @see MIMEBodyPart#BINARY + * @see MIMEBodyPart#E7BIT + * @see MIMEBodyPart#E8BIT + * @exception MIMEException If filename is null or textIS null. + * @exception FileNotFoundException If filename file does not exist + * @exception SecurityException If filename file can not be accessed + * @exception IOExcepton On IO Errors on filename or textIS. + */ + public MIMEMultiPart (InputStream textIS, String filename, int encoding) throws + MIMEException, FileNotFoundException, SecurityException, IOException + { + MIMEBasicPart l_txtPart; + MIMEBasicPart l_filePart; + FileInputStream l_fis; + fileMIMEType l_fmt; + + m_parsedPart = 0; + m_contentDisposition = UNINITIALIZED; + m_contentTransferEncoding = UNINITIALIZED; + m_needPreamble = false; + m_bodyParts = new Vector(); + m_boundary = null; + + if (textIS == null) + throw new MIMEException ("Invalid null InputStream"); + + if (filename == null) + throw new MIMEException ("Invalid null filename:" + filename); + + if ((encoding != -1) && (encoding < MIMEBodyPart.BASE64 || encoding > MIMEBodyPart.E8BIT)) + + throw new MIMEException ("Invalid MIME encoding:" + encoding); + this.setContentSubType ("mixed"); + + l_fis = new FileInputStream (filename); + + // The text-part! + l_txtPart = new MIMEBasicPart (MIMEBasicPart.TEXT); + + l_txtPart.setContentSubType ("plain"); + l_txtPart.setContentTypeParams ("charset=us-ascii"); + l_txtPart.setContentEncoding(MIMEBodyPart.E7BIT); + //l_txtPart.setContentDisposition(MIMEBodyPart.INLINE); + + l_txtPart.setBodyData (textIS); + + // The file-part + l_fmt = MIMEHelper.getFileMIMEType (filename); + + if (l_fmt == null) + throw new MIMEException ("Can't determine MIME info for file: " + filename); + + l_filePart = new MIMEBasicPart (l_fmt.content_type); + + l_filePart.setContentSubType (l_fmt.content_subtype); + + if (l_fmt.content_params != null) + l_filePart.setContentTypeParams (l_fmt.content_params); + else + l_filePart.setContentTypeParams (new String ("name=" + l_fmt.file_shortname)); + + if (encoding == -1) + l_filePart.setContentEncoding(l_fmt.mime_encoding); + else + l_filePart.setContentEncoding (encoding); + + if (l_fmt.content_type == MIMEBasicPart.TEXT); + //l_filePart.setContentDisposition (MIMEBodyPart.INLINE); + else + { + //l_filePart.setContentDisposition (MIMEBodyPart.ATTACHMENT); + l_filePart.setContentDispParams (new String ("filename=" + l_fmt.file_shortname)); + l_filePart.setContentDescription (l_fmt.file_shortname); + } + + // set body-data of this part + l_filePart.setBodyData (l_fis); + + // Add this part to multi-part + this.addBodyPart (l_txtPart, false); + this.addBodyPart (l_filePart, false); + } + + + +//=========================================================================== +// +// Methods overridden from MIMEBodyPart +// +//=========================================================================== + + /** + * Returns Content-Type of this MIME Part. Always returns the String "MultiPart". + */ + public String getContentType () + { + return (MULTIPART); + } + + +//=========================================================================== +// +// Methods Specific to this Class alone. +// +//=========================================================================== + + /** + * Returns the count of BodyParts in this MultiPart or + * Zero (0) if no parts exist. + */ + public int getBodyPartCount () + { + if (m_bodyParts != null) + return (m_bodyParts.size()); + else + return (0); + } + + /** + * Returns an Object of corresponding MIME BodyPart type at the + * specified index of this MultiPart. + * @param index Index for the BodyPart to return. Index ranges from 0 to (count_of_parts -1). + * @param clone Whether to return a reference to the internal BodyPart object or a cloned copy; + * if true: cloned copy; if false: reference to the BodyPart object. + * @exception MIMEException If invalid index. + * @see MIMEBasicPart + * @see MIMEMessagePart + * @see MIMEMultiPart + * @see MIMEMultiPart#getBodyPartCount + */ + public Object getBodyPart (int index, boolean clone) throws MIMEException + { + if (index >= m_bodyParts.size() || index < 0) + throw new MIMEException ("getBodyPart(). Index out of range: " + index); + + Object obj = m_bodyParts.elementAt (index); + + if (obj instanceof MIMEBasicPart) + { + MIMEBasicPart part = (MIMEBasicPart) obj; + + try + { + if (clone) + return (part.clone()); + else + return (part); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else if (obj instanceof MIMEMessagePart) + { + MIMEMessagePart part = (MIMEMessagePart) obj; + + try + { + if (clone) + return (part.clone()); + else + return (part); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else if (obj instanceof MIMEMultiPart) + { + MIMEMultiPart part = (MIMEMultiPart) obj; + + try + { + if (clone) + return (part.clone()); + else + return (part); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else + throw new MIMEException ("Invalid bodyPart!"); + } + + /** + * The following is an internal routine. Not to be exposed at the API. + * @exception MIMEException If the multipart is malformed. + * @exception IOException If IO error occurs. + */ + protected void putByteStream (OutputStream os, boolean needPreamble) + throws IOException, MIMEException + { + if (needPreamble == true) + m_needPreamble = true; + else + m_needPreamble = false; // In case we did not reset last pass due exception + + putByteStream (os); + + m_needPreamble = false; // reset after done. + } + + /** + * Outputs a byte stream for this Message in MIME format. + * Applies transfer encoding all bodyParts as applicable. + * @param os OutputStream to be written to. + * @exception IOException If an IO error occurs. + * @exception MIMEException If detects an error during encoding. + */ + public void putByteStream (OutputStream os) throws IOException, MIMEException + { + int l_bpCount = m_bodyParts.size(); + + if ((m_bodyParts == null) || (l_bpCount < 1)) + throw new MIMEException ("MIMEMultiPart.putByteStream(). No bodyParts!"); + + // Headers first + StringBuffer l_hdrbuf = new StringBuffer (HDRBUFSZ); + byte[] l_bytebuf; + String l_boundary; + boolean l_fQBounds=false; + + if (m_boundary != null && m_boundary.length() > 0) + { + l_boundary = m_boundary; + l_fQBounds = true; + } + else + l_boundary = generateBoundary(); + + // content-type + l_hdrbuf.append ("Content-Type: multipart/"); + + if (m_contentSubType != null) + l_hdrbuf.append (m_contentSubType); + else + throw new MIMEException ("putByteStream: No content-subtype." ); + + if (m_contentTypeParams != null && !m_contentTypeParams.startsWith ("boundary")) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentTypeParams); + l_hdrbuf.append ("; boundary="); + if (l_fQBounds) + l_hdrbuf.append ('"'); + l_hdrbuf.append (l_boundary); + if (l_fQBounds) + l_hdrbuf.append ('"'); + l_hdrbuf.append ("\r\n"); + } + else + { + l_hdrbuf.append ("; boundary="); + if (l_fQBounds) + l_hdrbuf.append ('"'); + l_hdrbuf.append (l_boundary); + if (l_fQBounds) + l_hdrbuf.append ('"'); + l_hdrbuf.append ("\r\n"); + } + + + // write the header to os + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Type: " + e.getMessage()); + } + + // contentID + if (m_contentID != null) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-ID: " + m_contentID); + l_hdrbuf.append ("\r\n"); + + try + { + // l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-ID: " + e.getMessage()); + } + } + + // contentDisposition + if (m_contentDisposition != UNINITIALIZED) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Disposition: " + + m_stringDisposition [m_contentDisposition]); + + if (m_contentDispParams != null) + { + l_hdrbuf.append ("; "); + l_hdrbuf.append (m_contentDispParams); + } + + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Disposition: " + e.getMessage()); + } + } + + // contentDescription + if (m_contentDescription != null) + { + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Description: " + m_contentDescription); + l_hdrbuf.append ("\r\n"); + + try + { + //l_bytebuf = l_hdrbuf.toString().getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Description: " + e.getMessage()); + } + } + + // This is the top-level multi-part. Add content-encoding + if (m_needPreamble == true && m_contentTransferEncoding != UNINITIALIZED) + { + boolean fwriteit = false; + switch (m_contentTransferEncoding) + { + case BINARY: + case E7BIT: + case E8BIT: + l_hdrbuf.setLength (0); + l_hdrbuf.append ("Content-Transfer-Encoding: " + + m_stringEncoding [m_contentTransferEncoding]); + l_hdrbuf.append ("\r\n"); + fwriteit = true; + break; + } + + + if (fwriteit) try + { + l_bytebuf = MIMEHelper.unicodeToASCII (l_hdrbuf.toString()); + os.write (l_bytebuf); + } + catch (Exception e) + { + throw new MIMEException ("Content-Encodding: " + e.getMessage()); + } + } + + + // blank-line after headers + //l_bytebuf = new String("\r\n").getBytes("iso-8859-1"); + os.write (CRLF); + + + if (m_preamble != null) + { + l_bytebuf = MIMEHelper.unicodeToASCII (m_preamble); + os.write (l_bytebuf); + } + // Write the line "This is a multi-part message in MIME format". + // This needs to be done only if it is the body of the message. Not when it is + // embedded part of another part (like another multi-part or a Messagepart). + // This is accomplished by passing a flag to putByteStream() by Message class. + else if (m_needPreamble == true && m_parsedPart==0) + { + String l_preamble = new String ("\r\nThis is a multi-part message in MIME format\r\n"); + // l_bytebuf = l_preamble.getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_preamble); + os.write (l_bytebuf); + m_needPreamble = false; + } + + String l_trailBoundary = "\r\n" + "--" + l_boundary + "--" + "\r\n"; + //l_boundary = "\r\n\r\n" + "--" + l_boundary + "\r\n"; + l_boundary = "\r\n" + "--" + l_boundary + "\r\n"; + //l_boundary = "--" + l_boundary + "\r\n"; + + for (int i = 0; i < l_bpCount; i++) + { + Object obj = m_bodyParts.elementAt (i); + + if (obj instanceof MIMEBasicPart) + { + //l_bytebuf = l_boundary.getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_boundary); + os.write (l_bytebuf); + + MIMEBasicPart part = (MIMEBasicPart) obj; + part.putByteStream (os); + } + else if (obj instanceof MIMEMessagePart) + { + //l_bytebuf = l_boundary.getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_boundary); + os.write (l_bytebuf); + + MIMEMessagePart part = (MIMEMessagePart) obj; + part.putByteStream (os); + } + else if (obj instanceof MIMEMultiPart) + { + //l_bytebuf = l_boundary.getBytes("iso-8859-1"); + l_bytebuf = MIMEHelper.unicodeToASCII (l_boundary); + os.write (l_bytebuf); + + MIMEMultiPart part = (MIMEMultiPart) obj; + part.putByteStream (os); + } + else + throw new MIMEException ("putByteStream(). Invalid bodyPart!"); + + } // end for + + l_bytebuf = MIMEHelper.unicodeToASCII (l_trailBoundary); + os.write (l_bytebuf); + } + + /** + * Deletes bodyPart at the requested index from this Multipart. + * Adjusts indices of any parts after the deleted part upwards + * as needed. + * @param index of the bodyPart to remove. + * @exception MIMEException if invalid index. + */ + public void deleteBodyPart (int index) throws MIMEException + { + if (index > m_bodyParts.size()) + throw new MIMEException ("deleteBodyPart(). Index out of range: " + index); + + m_bodyParts.removeElementAt (index); + } + + + /** + * Adds the specified filename file as a (MIMEBasicPart) BodyPart to this MultiPart. + * @param filename Name of file to add as bodyPart. + * @param encoding Preferred MIME encoding for this part. To select a default, use -1. + * @return The index at which this bodyPart is added. + * @see MIMEBodyPart#BASE64 + * @see MIMEBodyPart#QP + * @see MIMEBodyPart#BINARY + * @see MIMEBodyPart#E7BIT + * @see MIMEBodyPart#E8BIT + * @exception MIMEException If filename file is inaccessible or I/O errors. + * @exception FileNotFoundException If filename file does not exist + * @exception SecurityException If filename file cannot be accessed + * @exception IOExcepton On IO Errors on filename + */ + public int addBodyPart (String filename, int encoding) throws MIMEException, FileNotFoundException, SecurityException, IOException + { + MIMEBasicPart l_filePart; + FileInputStream l_fis; + fileMIMEType l_fmt; + + if (filename == null) + throw new MIMEException ("Invalid null filename:" + filename); + + if ((encoding != -1) && (encoding < MIMEBodyPart.BASE64 || encoding > MIMEBodyPart.E8BIT)) + + throw new MIMEException ("Invalid MIME encoding:" + encoding); + + l_fis = new FileInputStream (filename); + + l_fmt = MIMEHelper.getFileMIMEType (filename); + + if (l_fmt == null) + throw new MIMEException ("Can't determine MIME info for file: " + filename); + + l_filePart = new MIMEBasicPart (l_fmt.content_type); + + l_filePart.setContentSubType (l_fmt.content_subtype); + + if (l_fmt.content_params != null) + l_filePart.setContentTypeParams (l_fmt.content_params); + else + l_filePart.setContentTypeParams (new String ("name=" + filename)); + + if (encoding == -1) + l_filePart.setContentEncoding (l_fmt.mime_encoding); + else + l_filePart.setContentEncoding (encoding); + + if (l_fmt.content_type == MIMEBasicPart.TEXT) + l_filePart.setContentDisposition (MIMEBodyPart.INLINE); + else + { + l_filePart.setContentDisposition (MIMEBodyPart.ATTACHMENT); + l_filePart.setContentDispParams (new String ("filename=" + filename)); + l_filePart.setContentDescription (filename); + } + + // set body-data of this part + l_filePart.setBodyData (l_fis); + + // Add this part to multi-part + return addBodyPart (l_filePart, false); + } + + + /** + * Adds a MIMEBodyPart to this MultiPart. This part must already be constructed + * and fully set-up. As MIMEBodyPart is an abstract class, the object passed in the + * part parameter must be one of three concrete classes: MIMEBasicPart, MIMEMultiPart, or + * MIMEMessagePart. If not, a MIMEException is thrown. + * @param part bodyPart to add. + * @param clone Whether to store a reference to the passed object instead of cloning a copy; + * if true: clones a copy; if false: stores reference to the passed part object. + * @return The index at which to add this bodyPart. + * @exception MIMEException If part is malformed or is not an object of one of three + * classes: MIMEBasicPart, MIMEMultiPart, or MIMEMessagePart. + * @see MIMEBasicPart + * @see MIMEMultiPart + * @see MIMEMessagePart + */ + public int addBodyPart (MIMEBodyPart part, boolean clone) throws MIMEException + { + if (part == null) + throw new MIMEException ("addBodyPart(): null part passed!"); + + if (part instanceof MIMEBasicPart) + return addBodyPart ((MIMEBasicPart) part, clone); + else if (part instanceof MIMEMessagePart) + return addBodyPart ((MIMEMessagePart) part, clone); + else if (part instanceof MIMEMultiPart) + return addBodyPart ((MIMEMultiPart) part, clone); + else + throw new MIMEException ("addBodyPart(): Invalid part "); + } + + /** + * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been + * constructed and fully set-up + * @param part bodyPart to add. + * @return The index at which this bodyPart is added. + * @exception MIMEException If it is a malformed bodyPart (or) can not be added. + * @see MIMEBasicPart + */ + protected int addBodyPart (MIMEBasicPart part ) throws MIMEException + { + return addBodyPart( part, true ); + } + + + /** + * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been + * constructed and fully set-up. + * @param part bodyPart to add. + * @return The index at which this bodyPart is added. + * @param clone if false stores reference to passed object instead of cloning a copy. + * @exception MIMEException If it is a malformed bodyPart (or) can not be added. + * @see MIMEBasicPart + */ + protected int addBodyPart (MIMEBasicPart part, boolean clone) throws MIMEException + { + MIMEBasicPart l_part; + + if (part == null) + throw new MIMEException ("addBodyPart(): null part passed!"); + + if (clone) + { + try + { + l_part = (MIMEBasicPart) part.clone(); + m_bodyParts.addElement (l_part); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else + m_bodyParts.addElement (part); + + return (m_bodyParts.size() -1); + } + + + /** + * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been + * constructed and fully set-up + * @param part bodyPart to add. + * @return The index at which this bodyPart is added. + * @exception MIMEException If it is a malformed bodyPart (or) can not be added. + * @see MIMEBasicPart + */ + protected int addBodyPart (MIMEMultiPart part) throws MIMEException + { + return addBodyPart( part, true ); + } + + + /** + * Adds a MIMEMultiPart BodyPart to this MultiPart. This part should have been + * constructed and fully set-up. + * @param part bodyPart to add. + * @param clone if false stores reference to passed object instead of cloning a copy. + * @return The index at which this bodyPart is added. + * @exception MIMEException If it is a malformed bodyPart (or) can not be added. + * clone made optional + */ + protected int addBodyPart (MIMEMultiPart part, boolean clone) throws MIMEException + { + MIMEMultiPart l_part; + + if (part == null) + throw new MIMEException ("addBodyPart(): null part passed!"); + + if (clone) + { + try + { + l_part = (MIMEMultiPart) part.clone(); + m_bodyParts.addElement (l_part ); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else + m_bodyParts.addElement (part); + + return (m_bodyParts.size() -1); + } + + + /** + * Adds a MIMEBasicPart BodyPart to this MultiPart. This part should have been + * constructed and fully set-up. + * @param part bodyPart to add. + * @return The index at which this bodyPart is added. + * @exception MIMEException If it is a malformed bodyPart (or) can not be added. + * @see MIMEBasicPart + */ + protected int addBodyPart (MIMEMessagePart part) throws MIMEException + { + return addBodyPart( part, true ); + } + + /** + * Adds a MIMEMessagePart BodyPart to this MultiPart. This part should have been + * constructed and fully set-up. + * @param part bodyPart to add. + * @param clone if false stores reference to passed object instead of cloning a copy. + * @return The index at which this bodyPart is added. + * @exception MIMEException If it is a malformed bodyPart (or) can not be added. + */ + protected int addBodyPart (MIMEMessagePart part, boolean clone) throws MIMEException + { + MIMEMessagePart l_part; + + if (part == null) + throw new MIMEException ("addBodyPart(): null part passed!"); + + if (clone) + { + try + { + l_part = (MIMEMessagePart) part.clone(); + m_bodyParts.addElement (l_part); + } + catch (CloneNotSupportedException e) + { + throw new MIMEException (e.getMessage()); + } + } + else + m_bodyParts.addElement (part); + + return (m_bodyParts.size() -1); + } + + protected void setContentEncoding (int encoding) //throws MIMEException + { + switch (encoding) + { + case BINARY: + case E7BIT: + case E8BIT: + m_contentTransferEncoding = encoding; + break; + default: + //throw new MIMEException ("Invalid Content Transfer Encoding : " + encoding); + } + } + + /** + * Returns the preamble for this multi-part if present. + * Returns null if preamble does not exist for this part. + * @return Preamble for the multi-part if present, null otherwise. + */ + public String getPreamble () + { + return m_preamble; + } + + /** + * Sets the preamble for this multi-part. + * @param preamble Preamble string. + */ + public void setPreamble (String preamble) + { + m_preamble = preamble; + } + + protected void addPreamble (byte[] preamble, int len ) + { + if (m_preamble == null) + m_preamble = new String (preamble, 0, len); + else + { + m_preamble = new String (m_preamble + new String (preamble, 0, len)); + } + } + + protected void addPreamble (byte[] preamble) + { + if (m_preamble == null) + m_preamble = new String (preamble); + else + { + m_preamble = new String (m_preamble + new String (preamble)); + } + } + + /** + * Generates and returns a boundary string that can be used in multi-parts etc. + * @return The boundary string. + */ + private String generateBoundary () + { + Random l_rand = new Random (System.currentTimeMillis()); + long l_numboundary = l_rand.nextLong(); + String l_boundary = new String ("-----" + Long.toHexString(l_numboundary)); + return (l_boundary); + } + + + /** + * Clones an instance of this MIMEMultiPart object. + * @exception CloneNotSupportedException If thrown by constituent components. + */ + public Object clone () throws CloneNotSupportedException + { + MIMEMultiPart l_theClone = (MIMEMultiPart) super.clone(); + + // Take care of all other "reference"s. + if (m_bodyParts != null) + l_theClone.m_bodyParts = (Vector) m_bodyParts.clone(); + + if (m_contentSubType != null) + l_theClone.m_contentSubType = m_contentSubType; + + if (m_contentTypeParams != null) + l_theClone.m_contentTypeParams = m_contentTypeParams; + + if (m_contentID != null) + l_theClone.m_contentID = m_contentID; + + if (m_contentDispParams != null) + l_theClone.m_contentDispParams = m_contentDispParams; + + if (m_contentDescription != null) + l_theClone.m_contentDescription = m_contentDescription; + + m_needPreamble = false; + l_theClone.m_UserObject = m_UserObject; + + return (l_theClone); + + } // end clone () + + + // Should not be exposed at the API level. Internal only routines. + protected void setBoundary( String boundary ) + { + if ( boundary != null ) + m_boundary = boundary; + } + + protected String getBoundary() { return m_boundary; } + +} // end class MIMEMultiPart diff --git a/msgsdk/java/protocol/MIME/src/MIMEParser.java b/msgsdk/java/protocol/MIME/src/MIMEParser.java new file mode 100644 index 00000000000..51770f30b8a --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/MIMEParser.java @@ -0,0 +1,2834 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +/** + * The MIMEParser class represents a MIME parser. + * This generic parser takes an email message encoded in + * MIME and decodes it into a set of usable structures. + * @author Carson Lee + * @version 1.0 + * Dec 17,1997 + */ + +package netscape.messaging.mime; + +import java.io.FileInputStream; +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Vector; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.util.Enumeration; +import netscape.messaging.mime.MIMEMessage; +import netscape.messaging.mime.MIMEBasicPart; +import netscape.messaging.mime.MIMEMultiPart; +import netscape.messaging.mime.MIMEMessagePart; +import netscape.messaging.mime.MIMEHelper; +import netscape.messaging.mime.MIMEDataSink; +import netscape.messaging.mime.log; + + + + +public final class MIMEParser +{ + // used internally by parser to track information + final class mimeInfo + { + int m_type; // content type + byte[] m_name; // header name + byte[] m_value; // header value + Vector m_param; // header param + + mimeInfo( int type1, byte[] name1, byte[] value1 ) + { + m_type = type1; + m_name = name1; + m_value = value1; + m_param = new Vector(); + } + + mimeInfo() + { + m_type = 0; + m_name = null; + m_value = null; + m_param = new Vector(); + } + + mimeInfo( int type1, byte[] name1 ) + { + m_type = type1; + m_name = name1; + m_param = new Vector(); + } + } + + final static int MIME_INFO_CONTENT_TYPE = 1; + final static int MIME_INFO_CONTENT_SUBTYPE = 2; + final static int MIME_INFO_CONTENT_TRANSFER_ENCODING = 3; + final static int MIME_INFO_CONTENT_DISPOSITION = 4; + final static int MIME_INFO_CONTENT_ID = 5; + final static int MIME_INFO_CONTENT_DESCRIPTION = 6; + final static int MIME_INFO_CONTENT_BASE = 7; + final static int MIME_INFO_MIME_VERSION = 8; + + final static private int MIME_INFO = 31; + final static private int MIME_HEADER = 32; + final static private int MIME_XHEADER = 33; + final static private int MIME_MESSAGE_DATA = 34; + final static private int MIME_PARAM = 35; + final static private int MIME_BOUNDARY = 36; + final static private int MIME_CRLF = 37; + + final static private int MIME_UNINITIALIZED = -88; + final static private int MIME_CONTENT_PARSE_ALL = -99; + + final static private int NSMAIL_ERR_INVALIDPARAM = -1; + final static private int NSMAIL_ERR_OUTOFMEMORY = -2; + final static private int NSMAIL_OK = 0; + + final static private int NOT_A_BOUNDARY = 21; + final static private int START_BOUNDARY = 22; + final static private int END_BOUNDARY = 23; + final static int BUFFER_SIZE = 2048; + + final static int SUBTYPE_RFC822 = 1; + final static int SUBTYPE_EXTERNAL_BODY = 2; + final static byte[] mb_MultiPart = "Multipart".getBytes(); + //final static byte[] mb_MessagePart = "Messagepart".getBytes(); + final static byte[] mb_MessagePart = "Message".getBytes(); + final static byte[] mb_TextPart = "Text".getBytes(); + final static byte[] mb_AudioPart = "Audio".getBytes(); + final static byte[] mb_VideoPart = "Video".getBytes(); + final static byte[] mb_ImagePart = "Image".getBytes(); + final static byte[] mb_ApplPart = "Application".getBytes(); + private static final byte[] CRLF = "\r\n".getBytes(); + + private Vector m_messageData; // text buffer + private Vector m_mimeInfo; // message info + private Vector m_msgParts; // message info + + private MIMEMessage m_mimeMessage; // entire decoded mime message + private int m_nMessageType; // message type, ie: message, basicpart, etc. + + private Object m_currentMessage; // current message being processed + private MIMEBasicPart m_currentBasicPart; + private MIMEMultiPart m_currentMultiPart; + private MIMEMessagePart m_currentMessagePart; + + private int m_nCurrentMessageType; // current message type + + private boolean m_bStartData; // TRUE if start of data + private int m_emptyLineNo; // TRUE if current line is empty + private MIMEDataSink m_dataSink; // callback datasink + private byte[] m_leftoverBuffer; // leftover buffer + private byte[] m_inputBuffer; // inputbuffer + private int m_leftoverBytes; // left over bytes from previous parse + private int m_out_byte; // state info + private int m_out_bits; // state info + private boolean m_bParseEntireFile; // false if parsing in chunks + private byte[] m_QPLeftoverBuffer; // leftover buffer + private int m_QPLeftoverBytes; // left over bytes from previous parse + private Vector m_currentParent; // current message parent + private MIMEMessage m_currentMimeMessage; // current mime message + private boolean m_bDecodeData = true; + private boolean m_bLocalStorage = true; + private MIMEMessage m_nextMimeMessage; + private int m_lastBoundry; + private Vector m_mimeInfoQueue; + private Vector m_headerQueue; + private boolean m_qp = false; + private boolean m_readCR = false; + private Object m_headerParent; + private Object m_nextHeaderParent; + private String m_previousHeaderName; + private int m_messagePartSubType; + private boolean m_fSeenBoundary = false; + private boolean m_fEndMessageHeader = false; + + + // exposed routines ------------------------------------------------------------ + + + + /** + * Default Constructor. Use with static parsing + * when the entire message to be parsed is available. + * @see #parseEntireMessage + * + */ + public MIMEParser() + { + this( (MIMEMessage) null); + } + + + + + /** + * Parses an entire message from an input stream. + * @param input User's input stream; source of message to parse. + * @return New parsed MIMEMessage object. + * @exception MIMEException If any parameter is NULL. + */ + public MIMEMessage parseEntireMessage( InputStream input ) throws MIMEException + { + init( null ); + return (MIMEMessage) parseMimeMessage( input, MIME_CONTENT_PARSE_ALL ); + } + + + + + // internal routines ------------------------------------------------------------ + + + + + /** + * @param dataSink User's datasink for callbacks. Can not be null. + * @return New MIMEParser object + * @exception none + */ + protected MIMEParser (MIMEDataSink dataSink, boolean decodeData, boolean localStorage) throws MIMEException + { + this( (MIMEMessage) null ); + + if ( dataSink == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + m_dataSink = dataSink; + m_bParseEntireFile = false; + m_bDecodeData = decodeData; + m_bLocalStorage = localStorage; + } + + + + + /** + * Begin parse / reset parser. + * Only used for callbacks + * + * @author Carson Lee + * @version %I%, %G% + * + * @param none + * @return none + * @exception none + */ + protected void beginParse() throws MIMEException + { + m_leftoverBytes = 0; + m_QPLeftoverBytes = 0; + m_out_byte = 0; + m_out_bits = 0; + + if ( m_dataSink == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + reInit (null); + + //if ( m_bLocalStorage ) + // m_mimeMessage.setUserObject( m_dataSink.startMessage( m_mimeMessage ) ); + //else + m_mimeMessage.setUserObject( m_dataSink.startMessage() ); + } + + + + + /** + * Parse incoming data. Only used for callbacks + * + * @author Carson Lee + * @version %I%, %G% + * + * @param input User's inputstream. Data will be read off this inputstream. + * @return none + * @exception MIMEException + */ + protected void parse( InputStream input ) throws MIMEException + { + parseMimeMessage( input, MIME_CONTENT_PARSE_ALL ); + } + + + + /** + * End parse. Tell parse there is no more data. + * Used only for callbacks. + * + * @author Carson Lee + * @version %I%, %G% + * + * @param none + * @return none + * @exception MIMEException + */ + protected void endParse() throws MIMEException + { + decodeDataBuffer(); + + if ( m_dataSink == null ) + return; + + MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; + + // take care of leftover bytes + if ( m_nCurrentMessageType == MIMEMessage.BASICPART ) + { + MIMEBasicPart mimeBasicPart = (MIMEBasicPart) m_currentMessage; + + /* base64 */ + if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.BASE64 && m_out_bits > 0 ) + { + byte[] decodedBuffer = MIMEHelper.decodeBase64LeftOverBytes( m_out_bits, m_out_byte ); + saveBodyData( new ByteBuffer( decodedBuffer ), decodedBuffer.length, mimeBasicPart ); + } + +// will never happen +/* + else if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.QP && m_QPLeftoverBytes > 0 ) + { + int[] param = new int[2]; + param[0] = m_QPLeftoverBytes; + + byte[] decodedBuffer = MIMEHelper.decodeQPVector( m_messageData, + mimeBasicPart.getStartMessageDataIndex(), + mimeBasicPart.getEndMessageDataIndex(), + param, + m_QPLeftoverBuffer ); + if ( decodedBuffer != null ) + { + m_QPLeftoverBytes = param[0]; + param = null; + saveBodyData( new ByteBuffer( decodedBuffer ), param[1], mimeBasicPart ); + } + } +*/ + m_dataSink.endBasicPart( m.getUserObject() ); + } + else if ( m_nCurrentMessageType == MIMEMessage.MULTIPART ) + { + m_dataSink.endMultiPart( m.getUserObject() ); + } + else if ( m_nCurrentMessageType == MIMEMessage.MESSAGEPART ) + { + m_dataSink.endMessagePart( m.getUserObject() ); + m_msgParts.removeElement (m); + } + + + // systematically invoke end* callback on all pending parts. + int nCurrentParentType = getCurrentParentType(); + + while (nCurrentParentType != MIME_UNINITIALIZED) + { + switch (nCurrentParentType) + { + case MIMEMessage.MULTIPART: + //MIMEMultiPart mp = (MIMEMultiPart) m_currentParent.lastElement();; + //m_dataSink.endMultiPart (mp.getUserObject()); + // ingore. taken care of at END_BOUNDARY! + break; + case MIMEMessage.MESSAGEPART: + MIMEMessagePart msgP = (MIMEMessagePart) m_currentParent.lastElement(); + String cst = msgP.getContentSubType(); + if (cst.equalsIgnoreCase ("rfc822")) + { + // if it is rfc822. do endMessage() also + MIMEMessage mmm = msgP.getMessage(); + m_dataSink.endMessage (mmm.getUserObject()); + } + m_dataSink.endMessagePart (msgP.getUserObject()); + m_msgParts.removeElement (msgP); + break; + } + + m_currentParent.removeElementAt (m_currentParent.size() - 1); + nCurrentParentType = getCurrentParentType(); + //System.out.println ("placeyy> currentParentType = " + nCurrentParentType); + } + + // now check for any leftover ones still! + int partCount = m_msgParts.size(); + + while (partCount > 0) + { + MIMEMessagePart msgP = (MIMEMessagePart) m_msgParts.elementAt(partCount-1); + String cst = msgP.getContentSubType(); + + if (cst.equalsIgnoreCase ("rfc822")) + { + // if it is rfc822. do endMessage() also + MIMEMessage mmm = msgP.getMessage(); + m_dataSink.endMessage (mmm.getUserObject()); + } + + m_dataSink.endMessagePart (msgP.getUserObject()); + m_msgParts.removeElement (msgP); + + partCount--; + } + + + m_dataSink.endMessage( m_mimeMessage.getUserObject() ); + //checkForEmptyMessages( m_mimeMessage ); + } + + + + + /** + * General constructor + * + * @author Carson Lee + * @version %I%, %G% + * + * @param m User's MimeMessage object. Will populate data into this object. + * @return none + * @exception none + */ + protected MIMEParser( MIMEMessage m ) + { + init( m ); + } + + + + /** + * General constructor + * + * @author Carson Lee + * @version %I%, %G% + * + * @param m User's MimeMessage object. Will populate data into this object. + * @return none + * @exception none + */ + protected void init( MIMEMessage m ) + { + m_messageData = new Vector(); + m_mimeInfo = new Vector(); + m_msgParts = new Vector(); + m_mimeMessage = m == null ? new MIMEMessage() : m; + m_bStartData = false; + m_mimeMessage.m_parsedPart = 1; + m_nMessageType = MIME_UNINITIALIZED; + m_nCurrentMessageType = MIME_UNINITIALIZED; + m_currentMessage = null; + m_dataSink = null; + m_leftoverBuffer = new byte[2048]; + m_inputBuffer = new byte[2048]; + m_out_byte = 0; + m_out_bits = 0; + m_currentBasicPart = null; + m_currentMultiPart = null; + m_currentMessagePart = null; + m_bParseEntireFile = true; + m_currentParent = new Vector(); + m_currentMimeMessage = m_mimeMessage; + m_currentParent.addElement( m_mimeMessage ); + m_nextMimeMessage = null; + m_lastBoundry = START_BOUNDARY; + m_mimeInfoQueue = new Vector(); + m_headerQueue = new Vector(); + m_QPLeftoverBuffer = new byte[2]; + m_qp = false; + m_readCR = false; + m_headerParent = m_mimeMessage; + m_nextHeaderParent = null; + m_previousHeaderName = null; + m_messagePartSubType = SUBTYPE_RFC822; + m_fEndMessageHeader = false; + } + + + protected void reInit( MIMEMessage m ) + { + m_messageData = new Vector(); + m_mimeInfo = new Vector(); + m_msgParts = new Vector(); + m_mimeMessage = m == null ? new MIMEMessage() : m; + m_bStartData = false; + m_mimeMessage.m_parsedPart = 1; + m_nMessageType = MIME_UNINITIALIZED; + m_nCurrentMessageType = MIME_UNINITIALIZED; + m_currentMessage = null; + //m_dataSink = null; + m_leftoverBuffer = new byte[2048]; + m_inputBuffer = new byte[2048]; + m_out_byte = 0; + m_out_bits = 0; + m_currentBasicPart = null; + m_currentMultiPart = null; + m_currentMessagePart = null; + m_bParseEntireFile = true; + m_currentParent = new Vector(); + m_currentMimeMessage = m_mimeMessage; + m_currentParent.addElement( m_mimeMessage ); + m_nextMimeMessage = null; + m_lastBoundry = START_BOUNDARY; + m_mimeInfoQueue = new Vector(); + m_headerQueue = new Vector(); + m_QPLeftoverBuffer = new byte[2]; + m_qp = false; + m_readCR = false; + m_headerParent = m_mimeMessage; + m_nextHeaderParent = null; + m_previousHeaderName = null; + m_messagePartSubType = SUBTYPE_RFC822; + m_fEndMessageHeader = false; + } + + + + /** + * Parse user inputstream and return a populated data structure + * + * @author Carson Lee + * @version %I%, %G% + * + * @param input User's inputstream + * @param nMessageType

+    *               MIME_CONTENT_PARSE_ALL      if you want it to parse the entire message
+    *               MIMEMessage.BASICPART       for basicPart
+    *               MIMEMessage.MULTIPART       for multiPart
+    *               MIMEMessage.MESSAGEPART     for messagePart
+ * + * @return
MIMEMessage object if nMessageType = MIME_CONTENT_PARSE_ALL
+    *MIMEBasicPart object if nMessageType = MIMEMessage.BASICPART;
+    *MIMEMultiPart object if nMessageType = MIMEMessage.MULTIPART;
+    *MIMEMessagePart object if nMessageType = MIMEMessage.MESSAGEPART; 
+ * @exception MIMEException + */ + protected Object parseMimeMessage( InputStream input, int nMessageType ) throws MIMEException + { + byte ch = 0; + String s; + int type, lineLen, messageLen; + Object returnObject = null; + int i = 0, j = 0, ii; + boolean lastLine = false; + byte[] swap; + boolean bMoreData = true; + + if ( input == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + type = MIME_HEADER; + + try + { + messageLen = input.available(); + + for ( i = 0, j = m_leftoverBytes; bMoreData; ) + { + lineLen = input.read( m_inputBuffer ); + + if ( lineLen < m_inputBuffer.length ) + bMoreData = false; + + if ( lineLen < 0 ) + break; + + i = i + lineLen; + + for ( ii = 0; ii < lineLen; ii++ ) + { + ch = m_inputBuffer[ii]; + + if ( ch == -1 ) + break; + + // eat it + if ( !m_readCR && ch == '\r' ) + continue; + + else if ( ch == '\n' ) + { + // look for continuation + if ( MIMEHelper.bStringEquals( m_leftoverBuffer, "content-" ) ) + { + int jj = j > 0 ? j - 1 : 0; + char ch2; + + for ( ch2 = (char) m_leftoverBuffer[jj]; jj > 0 && Character.isWhitespace( ch2 ); ch2 = (char) m_leftoverBuffer[--jj] ) + ; + + // implement look ahead logic + if ( ( m_inputBuffer[ii+1] == ' ' || m_inputBuffer[ii+1] == '\t' ) && + !MIMEHelper.bStringEquals( m_leftoverBuffer, "content-transfer-encoding" ) ) + { +// System.out.println("continue = [" + new String( m_leftoverBuffer, 0, j) + "]" ); + m_leftoverBuffer[j++] = ch; + continue; + } + } + + if ( m_readCR ) + m_leftoverBuffer[j++] = ch; + + type = parseLine( m_leftoverBuffer, j, type, lastLine ); + j = 0; + + continue; + + } + + m_leftoverBuffer[j++] = ch; + } + } + } + catch ( IOException e ) + { + // rethrow to a common exception handler + throw new MIMEException( e.getMessage() ); + } + + // handle lastline + if ( m_dataSink == null && j != 0 ) + { + type = parseLine( m_leftoverBuffer, j, type, lastLine ); + } + + m_leftoverBytes = j; + + if ( m_mimeMessage != null && m_dataSink == null ) + checkForEmptyMessages( m_mimeMessage ); + + if ( m_mimeMessage != null && m_dataSink == null ) + { + decodeDataBuffer(); + + if ( nMessageType == MIME_CONTENT_PARSE_ALL ) + returnObject = m_mimeMessage; + + else if ( nMessageType == MIMEMessage.BASICPART || + nMessageType == MIMEMessage.MULTIPART || + nMessageType == MIMEMessage.MESSAGEPART ) + returnObject = m_mimeMessage.getBody(); + } + + // send data out right now using callbacks + else if ( m_dataSink != null && m_bStartData ) + { + decodeDataBuffer(); + } + + return returnObject; + } + + + + + /** + * Save decoded data + * + * @author Carson Lee + * @version %I%, %G% + * + * @param byteBuffer data buffer + * @param len number of bytes in the data buffer to save + * @param mimeBasicPart save data to this mimeBasicPart + * @return none + * @exception MIMEException + */ + private void saveBodyData( ByteBuffer byteBuffer, int len, MIMEBasicPart mimeBasicPart ) throws MIMEException + { + if ( byteBuffer == null || mimeBasicPart == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + if ( m_bLocalStorage ) + { + if ( m_bParseEntireFile ) + { + mimeBasicPart.setBodyData( byteBuffer ); + mimeBasicPart.setMessageDataLen( len ); + } + else + { + ByteBuffer b = mimeBasicPart.getDataBuf(); + + if ( b == null ) + { + mimeBasicPart.setBodyData( byteBuffer ); + mimeBasicPart.setMessageDataLen( len ); + } + else + { + b.append( byteBuffer ); + mimeBasicPart.setMessageDataLen( mimeBasicPart.getMessageDataLen() + len ); + } + } + } + + if (m_dataSink != null) + { + if (len>0) + m_dataSink.bodyData (mimeBasicPart.getUserObject(), byteBuffer.getInputStream(), len); + + if (mimeBasicPart.m_endData == true) + m_dataSink.endBasicPart (mimeBasicPart.getUserObject()); + + if (mimeBasicPart.m_parentContainer != null) + { + MIMEMultiPart mp = mimeBasicPart.m_parentContainer; + if (mp.m_endPart == true) + m_dataSink.endMultiPart (mp.getUserObject()); + else; // ignore taken care of at end_boundary + } + + } + else + mimeBasicPart.setDecodedData( true ); + } + + + + /** + * Decode message Data + * + * @author Carson Lee + * @version %I%, %G% + * + * @param none + * @return none + * @exception MIMEException + */ + private void decodeDataBuffer() throws MIMEException + { + int i; + int type2; + int len = 0; + int nIndex; + boolean finished = false; + mimeInfo mi; + MIMEMessagePart mimeMessagePart; + MIMEBasicPart mimeBasicPart; + MIMEMultiPart mimeMultiPart; + ByteBuffer newBuffer = null; + byte decodedBuffer[] = null; + byte line[]; + int param[]; + boolean bDecoded = false; + int nStart; + int nEnd; + + // no decoding + if (m_nCurrentMessageType != MIMEMessage.BASICPART || m_currentMessage == null) + return; + + mimeBasicPart = (MIMEBasicPart) m_currentMessage; + nStart = mimeBasicPart.getStartMessageDataIndex(); + nEnd = mimeBasicPart.getEndMessageDataIndex(); + + // already being here + if (mimeBasicPart.getDecodedData()) + return; + + /* base64 */ + if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.BASE64 && m_bDecodeData ) + { + param = new int[3]; + param[0] = m_out_byte; + param[1] = m_out_bits; + + decodedBuffer = MIMEHelper.decodeBase64Vector( m_messageData, + nStart, + nEnd, + mimeBasicPart.getMessageDataLen(), + param ); + + if ( decodedBuffer != null ) + { + m_out_byte = param[0]; + m_out_bits = param[1]; + len = param[2]; + param = null; + saveBodyData( new ByteBuffer( decodedBuffer, len ), len, mimeBasicPart ); + } + + bDecoded = true; + } + /* quoted printable */ + else if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.QP && m_bDecodeData ) + { + param = new int[2]; + param[0] = m_QPLeftoverBytes; + + decodedBuffer = MIMEHelper.decodeQPVector( m_messageData, + nStart, + nEnd, + param, + m_QPLeftoverBuffer ); + if ( decodedBuffer != null ) + { + m_QPLeftoverBytes = param[0]; + len = param[1]; + param = null; + saveBodyData( new ByteBuffer( decodedBuffer ), len, mimeBasicPart ); + } + + bDecoded = true; + } + /* base64 without decodeing */ + else if ( mimeBasicPart.getContentEncoding() == MIMEBodyPart.BASE64 ) + { + if ( nEnd >= nStart && nStart != MIMEBasicPart.UNINITIALIZED ) + { + // get message size + for (i = nStart, len = 0; i <= nEnd; i++) + { + line = (byte[]) m_messageData.elementAt( i ); + //len = len + line.length; + len = len + line.length + 2; // for CRLF + } + + newBuffer = new ByteBuffer( len ); + + if ( newBuffer != null ) + { + for ( i = nStart; i <= nEnd; i++ ) + { + line = (byte[]) m_messageData.elementAt( i ); + newBuffer.append( line ); + newBuffer.append( CRLF ); + } + } + + saveBodyData( newBuffer, len, mimeBasicPart ); + bDecoded = true; + } + } + /* plain buffer creation */ + else + { + boolean fQP = false; + + if (mimeBasicPart.getContentEncoding() == MIMEBodyPart.QP) + fQP = true; + + if ( nEnd >= nStart && nStart != MIMEBasicPart.UNINITIALIZED ) + { + // get message size + for ( i = nStart, len = 0; i <= nEnd; i++ ) + { + line = (byte[]) m_messageData.elementAt( i ); + //len = len + line.length + 1; + len = len + line.length; + if (!fQP) + len = len + 2; + } + + newBuffer = new ByteBuffer( len ); + + byte b = '\n'; + byte r = '\r'; + + if ( newBuffer != null ) + { + for ( i = nStart; i <= nEnd; i++ ) + { + line = (byte[]) m_messageData.elementAt( i ); + newBuffer.append( line ); + if (!fQP) + { + newBuffer.append(r); + newBuffer.append(b); + } + } + } + + saveBodyData( newBuffer, len, mimeBasicPart ); + bDecoded = true; + } + } + + if ( bDecoded ) + { + if ( m_dataSink == null ) + mimeBasicPart.setDecodedData( true ); + else + { + // reset + mimeBasicPart.setStartMessageDataIndex( MIMEBasicPart.UNINITIALIZED ); + mimeBasicPart.setEndMessageDataIndex( MIMEBasicPart.UNINITIALIZED ); + } + } + } + + + + + + /** + * Parse one line of data + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input line + * @param len length of line + * @param type line type for the previous line
+    *   MIME_HEADER     header line
+    *   MIME_INFO       mime info line
+ * + * @return line type for current line + * @exception MIMEException + */ + private int parseLine( byte s[], int len, int type, boolean lastLine ) throws MIMEException + { + int i; + int type2; + int nIndex; + boolean finished = false; + mimeInfo mi; + + MIMEMessagePart mimeMessagePart; + MIMEBasicPart mimeBasicPart; + MIMEMultiPart mimeMultiPart; + MIMEMessage mimeMessage; + MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; + + if ( s == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + + +// debug +/* +//if ( MIMEHelper.bStringEquals( s, 7, "Subject" ) ) +//if ( MIMEHelper.bStringEquals (s, 13, "--mail.sleepy")) +String ss = new String(s,0,len); +System.out.println (ss); +boolean fRightHdr = false; +//if (MIMEHelper.bStringEquals (s, "X-Original-In-reply-to")) +if (MIMEHelper.bStringEquals (s, "Content-Type: message/rfc822")) +{ + ss = new String(s,0,len); + fRightHdr = true; +} +else if (MIMEHelper.bStringEquals (s, "Content-type: message/external-body")) +{ + ss = new String(s,0,len); + fRightHdr = false; +} +*/ + + /* ignore additional header info */ + if ( ( type == MIME_INFO || type == MIME_HEADER || type == MIME_XHEADER )&& ( len > 0 &&( s[0] == '\t' || s[0] == ' ' ) ) ) + { + if (!(m_nCurrentMessageType == MIMEMessage.BASICPART && m_bStartData == true)) + { + Header h[]; + + // continuation headers + if ( m_headerParent instanceof MIMEBasicPart ) + { + mimeBasicPart = (MIMEBasicPart) m_currentMessage; + mimeBasicPart.addHeader( m_previousHeaderName, new String ("\r\n" + new String( s, 0, len ) )); + } + else if ( m_headerParent instanceof MIMEMessagePart ) + { // Ignore!! + // mimeMessagePart = (MIMEMessagePart) m_currentMessage; + // h = mimeMessagePart.getAllHeaders(); + // mimeMessagePart.addHeader( m_previousHeaderName, new String( s, 0, len ) ); + + //m_currentMimeMessage.addHeader( m_previousHeaderName, new String( s, 0, len ) ); + m_currentMimeMessage.addHeader( m_previousHeaderName, new String ("\r\n" + new String( s, 0, len ) )); + } + else + { + if (m_previousHeaderName == null) + { + appToLastHdrOnQueue (MIMEHelper.byteSubstring( s, 0, len)); + } + else + { + m_currentMimeMessage.addHeader( m_previousHeaderName, new String ("\r\n" + new String( s, 0, len ) )); + if (m_dataSink != null) + { + m_dataSink.addHeader (m_currentMimeMessage.getUserObject(), + m_previousHeaderName.getBytes(), + MIMEHelper.byteSubstring(s, 0, len)); + } + } + + } +/* + if ( h != null && h[ h.length - 1 ] != null ) + { + h[ h.length - 1 ].setValue( h[ h.length - 1 ].getValue() + "\n" + new String( s,0,len ) ); + } +*/ + return type; + } + } + else if (( type == MIME_INFO || type == MIME_HEADER || type == MIME_XHEADER ) && m_dataSink != null) + { + // a new header with same name + m_previousHeaderName = null; + } + + + if ( len == 0 ) + { + m_emptyLineNo++; + + /* end of header callback (only on top level MimeMessage) */ + if (m_emptyLineNo == 1 && m_fEndMessageHeader == false) + { + m_fEndMessageHeader = true; + if (m_dataSink != null) + m_dataSink.endMessageHeader (m_mimeMessage.getUserObject()); + } + } + + if ( len == 0 && !m_bStartData ) + { + int nCurrentParentType = getCurrentParentType(); + + if ( m_qp && m_emptyLineNo == 1 ) + m_readCR = true; + + //if ( m_emptyLineNo == 2 ) + if (m_emptyLineNo >= 2 && !(m_nCurrentMessageType == MIMEMessage.MULTIPART && m_fSeenBoundary==false)) + { + m_bStartData = true; + } + else if (m_emptyLineNo == 1 && nCurrentParentType == MIMEMessage.MULTIPART && m_fSeenBoundary == true) + { + if (m_nCurrentMessageType == MIME_UNINITIALIZED) + m_bStartData = true; + } + /*else if (m_emptyLineNo == 1 && m_nCurrentMessageType == MIMEMessage.MULTIPART) This broke mime11 */ + else if (m_emptyLineNo > 1 && m_nCurrentMessageType == MIMEMessage.MULTIPART) + { + //System.out.println ("m_fSeenBoundary=" + m_fSeenBoundary); + + if (m_fSeenBoundary == true) + m_bStartData = true; + else + { + mimeMultiPart = (MIMEMultiPart) m_currentMessage; + s[0] = '\r'; + s[1] = '\n'; + mimeMultiPart.addPreamble (s, 2); + return MIME_CRLF; + } + + } + + else if ( m_emptyLineNo == 1 ) + { + // change mimeMessage parent + if ( m_nextMimeMessage != null ) + { + // change parent only after first blank line + m_currentMimeMessage = m_nextMimeMessage; + m_nextMimeMessage = null; + } + + // insert headers from queue + if ( m_nCurrentMessageType != MIME_UNINITIALIZED && m_headerQueue.size() > 0 ) + { + if ( m_headerQueue.size() > 0 ) + { + for ( i = 0; i < m_headerQueue.size(); i++ ) + { + mi = (mimeInfo) m_headerQueue.elementAt(i); + addHeader( mi.m_name, mi.m_value, false ); + } + + m_headerQueue.removeAllElements(); + } + } + + // change header parent + if ( m_nextHeaderParent != null && !( m_nextHeaderParent instanceof MIMEMultiPart) ) + m_headerParent = m_nextHeaderParent; + } + } + + if ( MIMEHelper.bStringEquals( s, len, "content-type" ) ) + { + if (m_emptyLineNo > 0 && m_nCurrentMessageType != MIMEMessage.MESSAGEPART) + m_emptyLineNo = 0; + } + + /* boundary */ + if ( ( type == MIME_INFO || type == MIME_HEADER || type == MIME_XHEADER ) && + len == 0 && m_nCurrentMessageType != MIME_UNINITIALIZED && m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) + { + if ( m_nCurrentMessageType == MIMEMessage.MESSAGEPART || m_nCurrentMessageType == MIMEMessage.MULTIPART ) + { + if ( m_emptyLineNo == 1 || m_emptyLineNo == 2 ) + type = MIME_HEADER; + else + { + mi = new mimeInfo(); + + // content type + mi.m_type = MIME_INFO; + mi.m_name = MIMEHelper.stringToByte( "content-type" ); + mi.m_value = MIMEHelper.stringToByte( "text/plain" ); + setData( mi ); + parseMimeInfo( mi ); + m_mimeInfo.addElement( mi ); + + // content type + //mi.m_type = MIME_INFO; + //mi.m_name = MIMEHelper.stringToByte( "content-transfer-encoding" ); + //mi.m_value = MIMEHelper.stringToByte( "7bit" ); + //setData( mi ); + //parseMimeInfo( mi ); + //m_mimeInfo.addElement( mi ); + + m_bStartData = true; + m_fSeenBoundary = false; + } + } + + else if ( m_nCurrentMessageType == MIMEMessage.BASICPART ) + { + m_bStartData = true; + m_fSeenBoundary = false; + } + + if ( m_emptyLineNo == 1 ) + return type; + } + + if ( !m_bStartData && len == 0 ) + { + // Add to preamble if multi-part + if (m_nCurrentMessageType == MIMEMessage.MULTIPART) + { + m_emptyLineNo = 0; + mimeMultiPart = (MIMEMultiPart) m_currentMessage; + + s[0] = '\r'; + s[1] = '\n'; + mimeMultiPart.addPreamble (s, 2); + } + + return MIME_CRLF; + } + + /* -------------------------- identify line ----------------------- */ + type2 = checkForLineType( s, len, m_bStartData ); + + if (type2 == START_BOUNDARY) + { + m_fSeenBoundary = true; + m_emptyLineNo = 0; + if (m_nCurrentMessageType == MIMEMessage.MESSAGEPART && + m_messagePartSubType == SUBTYPE_EXTERNAL_BODY) + m_bStartData = false; + + // give the boundary + if (m_nCurrentMessageType == MIMEMessage.MULTIPART && m_dataSink != null) + { + MIMEMultiPart mp = (MIMEMultiPart) m_currentMessage; + m_dataSink.boundary (mp.getUserObject(), (mp.getBoundary()).getBytes()); + } + else if (m_dataSink != null) + { + int nCurrentParentType = getCurrentParentType(); + + if (nCurrentParentType == MIMEMessage.MULTIPART) + { + MIMEMultiPart mp = (MIMEMultiPart) m_currentParent.lastElement();; + m_dataSink.boundary (mp.getUserObject(), (mp.getBoundary()).getBytes()); + } + } + } + + // start of simple basicpart message + if ( type == MIME_CRLF && m_nCurrentMessageType == MIME_UNINITIALIZED && m_lastBoundry == START_BOUNDARY && + type2 == MIME_MESSAGE_DATA && ( m_emptyLineNo == 1 || m_emptyLineNo == 2 ) && + m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) + { + mi = new mimeInfo(); + + // content type + mi.m_type = MIME_INFO; + mi.m_name = MIMEHelper.stringToByte( "content-type" ); + mi.m_value = MIMEHelper.stringToByte( "text/plain" ); + setData( mi ); + parseMimeInfo( mi ); + m_mimeInfo.addElement( mi ); + m_bStartData = true; + m_fSeenBoundary = false; + + if ( m_emptyLineNo == 1 || m_emptyLineNo == 2 ) + { + // insert headers from queue + if ( m_headerQueue.size() > 0 ) +// if ( m_nCurrentMessageType != MIME_UNINITIALIZED && m_headerQueue.size() > 0 ) + { + if ( m_nextHeaderParent != null ) + m_headerParent = m_nextHeaderParent; + + for ( i = 0; i < m_headerQueue.size(); i++ ) + { + mi = (mimeInfo) m_headerQueue.elementAt(i); + addHeader( mi.m_name, mi.m_value, false ); + } + + m_headerQueue.removeAllElements(); + } + + // change header parent + if ( m_nextHeaderParent != null ) + m_headerParent = m_nextHeaderParent; + + m_emptyLineNo = 0; // reset for headers + } + } + + /* start boundary */ + if ( !m_bStartData && type2 == START_BOUNDARY ) + { + unwindCurrentParent( s, false ); + + m_out_byte = 0; + m_out_bits = 0; + m_leftoverBytes = 0; + m_QPLeftoverBytes = 0; + m_emptyLineNo = 0; + m_nCurrentMessageType = MIME_UNINITIALIZED; + m_lastBoundry = START_BOUNDARY; + m_fSeenBoundary = true; + m_qp = false; + m_readCR = false; + m_messagePartSubType = SUBTYPE_RFC822; + + return MIME_INFO; + } + + /* end boundary */ + else if ( type2 == END_BOUNDARY || type2 == START_BOUNDARY ) + { + + if ( m_dataSink != null ) + { + switch( m_nCurrentMessageType ) + { + case MIMEMessage.BASICPART: + //m_dataSink.endBasicPart( m.getUserObject() ); break; + ((MIMEBasicPart)m).m_endData = true; break; + case MIMEMessage.MULTIPART: + if (type2 == END_BOUNDARY) + m_dataSink.endMultiPart (m.getUserObject()); + break; + case MIMEMessage.MESSAGEPART: + m_dataSink.endMessagePart (m.getUserObject()); + m_msgParts.removeElement (m); + break; + } + + + if (type2 == END_BOUNDARY) + { + int nCurrentParentType = getCurrentParentType(); + + if (nCurrentParentType == MIMEMessage.MULTIPART) + { + MIMEMultiPart mp = (MIMEMultiPart) m_currentParent.lastElement();; + if (m_nCurrentMessageType != MIMEMessage.BASICPART) + m_dataSink.endMultiPart (mp.getUserObject()); + else + { + mp.m_endPart = true; + ((MIMEBasicPart)m).m_parentContainer = mp; + } + } + //else if (m_nCurrentMessageType == MIMEMessage.MULTIPART) + //{ + // MIMEMultiPart mp = (MIMEMultiPart) m_currentMessage; + // m_dataSink.endMultiPart (mp.getUserObject()); + // System.out.println ("at END_BOUNDARY() nCurrentParentType= " + nCurrentParentType); + // System.out.println ("at END_BOUNDARY() m_nCurrentMessageType= " + m_nCurrentMessageType); + //} + } + } + + if ( type2 == END_BOUNDARY ) + { + unwindCurrentParent( s, true ); + m_lastBoundry = END_BOUNDARY; + m_fSeenBoundary = false; + } + else + m_lastBoundry = START_BOUNDARY; + + m_emptyLineNo = 0; + m_bStartData = false; + decodeDataBuffer(); + m_nCurrentMessageType = MIME_UNINITIALIZED; + m_qp = false; + m_readCR = false; + m_messagePartSubType = SUBTYPE_RFC822; + + return type2; + } + + /* message data */ + else if ( m_bStartData ) + { + // clone + byte sss[] = new byte[len]; + System.arraycopy( s, 0, sss, 0, len ); + m_messageData.addElement( sss ); + nIndex = m_messageData.size() - 1; + + if (m_nCurrentMessageType == MIME_UNINITIALIZED) + { + int nCurrentParentType = getCurrentParentType(); + + //System.out.println ("m_nCurrentMessageType== UNINIT"); + //System.out.println ("nCurrentParentType= " + nCurrentParentType); + + if (nCurrentParentType == MIMEMessage.MULTIPART) + { + mi = new mimeInfo(); + + // content type + mi.m_type = MIME_INFO; + mi.m_name = MIMEHelper.stringToByte("content-type"); + mi.m_value = MIMEHelper.stringToByte("text/plain"); + setData( mi ); + parseMimeInfo( mi ); + m_mimeInfo.addElement( mi ); + } + } + + if ( m_nCurrentMessageType == MIMEMessage.BASICPART && m_currentMessage != null ) + { + mimeBasicPart = (MIMEBasicPart) m_currentMessage; + + if ( mimeBasicPart.getStartMessageDataIndex() == MIMEBasicPart.UNINITIALIZED ) + mimeBasicPart.setStartMessageDataIndex( nIndex ); + + mimeBasicPart.setEndMessageDataIndex( nIndex ); + + if ( lastLine ) + { + decodeDataBuffer(); + + if ( m_dataSink != null ) + { + switch( m_nCurrentMessageType ) + { + case MIMEMessage.BASICPART: + m_dataSink.endBasicPart( m.getUserObject() ); break; + case MIMEMessage.MULTIPART: m_dataSink.endMultiPart( m.getUserObject() ); break; + case MIMEMessage.MESSAGEPART: + m_dataSink.endMessagePart( m.getUserObject() ); + m_msgParts.removeElement (m); + break; + } + } + } + } + + return MIME_MESSAGE_DATA; + } + + // add preamble + if (m_emptyLineNo> 0 && m_nCurrentMessageType == MIMEMessage.MULTIPART && ! m_fSeenBoundary) + { + mimeMultiPart = (MIMEMultiPart) m_currentMessage; + s[len] = '\n'; + mimeMultiPart.addPreamble(s, len + 1); + return type2; + } + + + mi = new mimeInfo(); + mi.m_type = type2; + + boolean startQuote = false; + + for ( i = 0; i < len && !finished; i++ ) + { + if ( s[i] == '"' ) + startQuote = startQuote ? false : true; + + if ( s[i] == ':' && !startQuote ) + { + mi.m_name = MIMEHelper.byteSubstring( s, 0, i ); + + /* remove junk spaces */ + for ( ++i; i < len && s[i] != 0 && Character.isWhitespace( (char) s[i] ); i++ ) + ; + + mi.m_value = MIMEHelper.byteSubstring( s, i, len - i ); + + if ( mi.m_type == MIME_INFO ) + { + setData( mi ); + + if ( m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) + { + parseMimeInfo( mi ); + m_mimeInfo.addElement( mi ); + } + } + + else if ( ( mi.m_type == MIME_XHEADER || mi.m_type == MIME_HEADER ) && m_headerParent != null ) + { + addHeader( mi.m_name, mi.m_value, true ); + m_emptyLineNo = 0; + } + + finished = true; + } + } + + // add preamble + if ( !finished && m_nCurrentMessageType == MIMEMessage.MULTIPART ) + { + m_emptyLineNo = 0; + mimeMultiPart = (MIMEMultiPart) m_currentMessage; + s[len] = '\n'; + mimeMultiPart.addPreamble( s, len + 1 ); + } + + mi = null; + + if ( lastLine ) + { + decodeDataBuffer(); + + if ( m_dataSink != null ) + { + switch( m_nCurrentMessageType ) + { + case MIMEMessage.BASICPART: + m_dataSink.endBasicPart( m.getUserObject() ); break; + case MIMEMessage.MULTIPART: m_dataSink.endMultiPart( m.getUserObject() ); break; + case MIMEMessage.MESSAGEPART: + m_dataSink.endMessagePart( m.getUserObject() ); + m_msgParts.removeElement (m); + break; + } + } + } + + return type2; + } + + + private void appToLastHdrOnQueue (byte value[]) + { + mimeInfo mi; + + int i = m_headerQueue.size(); + + if (i > 0) + { + mi = (mimeInfo) m_headerQueue.elementAt(i-1); + m_headerQueue.removeElementAt (i-1); + String val = new String (mi.m_value); + val = val.concat ("\r\n"); + String s2 = new String (value); + val = val.concat (s2); + //val.concat (new String (value)); + mi.m_value = val.getBytes(); + m_headerQueue.addElement( mi ); + } + } + +//add cont * headers to the bodypart. used by MessagePart only at this point. +private void addContHeader (MIMEBodyPart bp, mimeInfo mi) +{ + try + { + if (MIMEHelper.bStringEquals (mi.m_name, "content-description")) + { + bp.setContentDescription (new String (mi.m_value)); + } + else if (MIMEHelper.bStringEquals (mi.m_name, "content-disposition")) + { + int disp = translateDispType (mi.m_value); + bp.setContentDisposition (disp); + } + else if (MIMEHelper.bStringEquals (mi.m_name, "content-id")) + { + bp.setContentID (new String (mi.m_value)); + } + else if (MIMEHelper.bStringEquals (mi.m_name, "content-md5")) + { + if (bp instanceof MIMEBasicPart) + ((MIMEBasicPart)bp).setContentMD5 (new String (mi.m_value)); + } + else if (MIMEHelper.bStringEquals (mi.m_name, "content-transfer-encoding")) + { + //int encoding = mime_translateMimeEncodingType (mi.m_value); + //bp.setContentEncoding (encoding); + } + } + catch (Exception e) + { + // ignore + } +} + + + /** + * add header + * + * @author Carson Lee + * @version %I%, %G% + * + * @param mi.name header name + * @param mi.value header value + * @return none + * @exception MIMEException + */ + private void addHeader( byte name[], byte value[], boolean addToQueue ) throws MIMEException + { + MIMEMessagePart mimeMessagePart; + MIMEBasicPart mimeBasicPart; + MIMEMessage mimeMessage; + mimeInfo mi; + + if ( name == null || value == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + if (m_nCurrentMessageType == MIME_UNINITIALIZED && m_headerParent == m_mimeMessage) + { + m_mimeMessage.addRHeader( new String( name), new String( value ) ); + + if (m_dataSink != null) + { + if (m_previousHeaderName != null && m_previousHeaderName.equals (new String (name))) + m_dataSink.addHeader (m_mimeMessage.getUserObject(), name, value ); + else + { + m_dataSink.header (m_mimeMessage.getUserObject(), name, value ); + } + } + + m_previousHeaderName = new String( name ); + + return; + } + + // insert headers from queue, restore header order + if ( m_nCurrentMessageType != MIME_UNINITIALIZED && addToQueue && m_headerQueue.size() > 0 ) + { + for ( int i = 0; i < m_headerQueue.size(); i++ ) + { + mi = (mimeInfo) m_headerQueue.elementAt(i); + addHeader( mi.m_name, mi.m_value, false ); + } + + m_headerQueue.removeAllElements(); + } + + // add to header queue + if ( m_nCurrentMessageType == MIME_UNINITIALIZED && addToQueue ) + { + mi = new mimeInfo(); + + if ( mi != null ) + { + mi.m_name = name; + mi.m_value = value; + m_headerQueue.addElement( mi ); + } + } + + else if ( m_headerParent instanceof MIMEMessage ) + { + mimeMessage = (MIMEMessage) m_headerParent; + mimeMessage.addRHeader( new String( name ), new String( value ) ); + + if (m_dataSink != null) + { + + if (m_previousHeaderName != null && m_previousHeaderName.equals (new String(name))) + m_dataSink.addHeader (mimeMessage.getUserObject(), name, value); + else + m_dataSink.header (mimeMessage.getUserObject(), name, value); + + + } + + m_previousHeaderName = new String( name ); + } + + else if ( m_emptyLineNo > 0 || (m_headerParent instanceof MIMEBasicPart && m_fSeenBoundary)) + { + if ( m_headerParent instanceof MIMEBasicPart ) + { + mimeBasicPart = (MIMEBasicPart) m_headerParent; + mimeBasicPart.setHeader( new String( name ), new String( value ) ); + m_previousHeaderName = new String( name ); + + if ( m_dataSink != null ) + { + m_dataSink.header( mimeBasicPart.getUserObject(), name, value ); + } + } + else if ( m_headerParent instanceof MIMEMessagePart || m_headerParent instanceof MIMEMultiPart ) + { + mimeMessage = (MIMEMessage) m_currentMimeMessage; + mimeMessage.addRHeader( new String( name), new String( value ) ); + + if ( m_dataSink != null ) + { + if (m_previousHeaderName != null && m_previousHeaderName.equals (new String (name))) + m_dataSink.addHeader (mimeMessage.getUserObject(), name, value); + else + { + m_dataSink.header(mimeMessage.getUserObject(), name, value); + } + } + + m_previousHeaderName = new String( name ); + } + } + } + + + + /** + * fill in data fields from pMimeinfo + * + * @author Carson Lee + * @version %I%, %G% + * + * @param mi mimeInfo structure + * @return none + * @exception MIMEException + */ + private void setData( mimeInfo mi ) throws MIMEException + { + MIMEMessagePart mimeMessagePart; + MIMEBasicPart mimeBasicPart; + byte[] param[]; + int contentDisposition; + MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; + + if ( mi == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + param = new byte[1][]; + + if ( m_messagePartSubType == SUBTYPE_EXTERNAL_BODY && m_nCurrentMessageType == MIMEMessage.MESSAGEPART ) + { + // add as external body headers + mimeMessagePart = (MIMEMessagePart) m_currentMessage; + + if ( m_emptyLineNo >= 1 ) + { + + mimeMessagePart.setExtBodyHeader (new String( mi.m_name), + new String( mi.m_value ) ); + m_previousHeaderName = new String( mi.m_name ); + } + else + { + addContHeader (mimeMessagePart, mi); + //addHeader( mi.m_name, mi.m_value, true ); + } + } + + //if ( m_messagePartSubType == SUBTYPE_EXTERNAL_BODY && m_nCurrentMessageType == MIMEMessage.MESSAGEPART ) + //{ + // // add as external body headers + // mimeMessagePart = (MIMEMessagePart) m_currentMessage; + // mimeMessagePart.setExtBodyHeader( new String( mi.m_name), new String( mi.m_value ) ); + // m_previousHeaderName = new String( mi.m_name ); + //} + + /* create message structure */ + else if ( MIMEHelper.bStringEquals( mi.m_name, "content-type" ) ) + { + newMessageStructure( mi.m_value ); + + // add from queue + if ( m_mimeInfoQueue.size() > 0 && m_messagePartSubType != SUBTYPE_EXTERNAL_BODY ) + { + for ( int i = 0; i < m_mimeInfoQueue.size(); i++ ) + { + setData( (mimeInfo) m_mimeInfoQueue.elementAt(i) ); + } + + m_mimeInfoQueue.removeAllElements(); + } + } + + // no structure created yet + else if ( m_currentMessage == null ) + return; + + else if ( MIMEHelper.bStringEquals( mi.m_name, "content-description" ) ) + { + if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) + m_mimeInfoQueue.addElement( mi ); + else + { + m.setContentDescription( new String( mi.m_value ) ); + + if ( m_dataSink != null ) + m_dataSink.contentDescription( m.getUserObject(), mi.m_value ); + } + } + + else if ( MIMEHelper.bStringEquals( mi.m_name, "content-disposition" ) ) + { + if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) + m_mimeInfoQueue.addElement( mi ); + else + { + contentDisposition = translateDispType( mi.m_value, param ); + + if ( contentDisposition != NSMAIL_ERR_INVALIDPARAM ) + { + m.setContentDisposition( contentDisposition ); + + if ( m_dataSink != null ) + m_dataSink.contentDisposition( m.getUserObject(), contentDisposition ); + + if ( param[0] != null ) + { + m.setContentDispParams( new String( param[0] ) ); + + if ( m_dataSink != null ) + m_dataSink.contentDispParams( m.getUserObject(), param[0] ); + } + } + } + } + + else if ( MIMEHelper.bStringEquals( mi.m_name, "content-id" ) ) + { + if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) + m_mimeInfoQueue.addElement( mi ); + else + { + m.setContentID( new String( mi.m_value ) ); + + if ( m_dataSink != null ) + m_dataSink.contentID( m.getUserObject(), mi.m_value ); + } + } + + else if ( MIMEHelper.bStringEquals( mi.m_name, "content-transfer-encoding" ) ) + { + if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) + m_mimeInfoQueue.addElement( mi ); + else + { + if ( m_currentMessage instanceof MIMEBasicPart ) + { + mimeBasicPart = (MIMEBasicPart) m_currentMessage; + int encoding; + + encoding = mime_translateMimeEncodingType( mi.m_value ); + mimeBasicPart.setContentEncoding( encoding ); + + if ( encoding == MIMEBodyPart.QP ) + m_qp = true; + + if ( m_dataSink != null ) + m_dataSink.contentEncoding( mimeBasicPart.getUserObject(), encoding ); + } + + else if ( m_currentMessage instanceof MIMEMessagePart ) + { + mimeMessagePart = (MIMEMessagePart) m_currentMessage; + int encoding; + + encoding = mime_translateMimeEncodingType( mi.m_value ); + mimeMessagePart.setContentEncoding( encoding ); + if ( m_dataSink != null ) + m_dataSink.contentEncoding( mimeMessagePart.getUserObject(), encoding ); + } + else if ( m_currentMessage instanceof MIMEMultiPart ) + { + MIMEMultiPart mimeMultiPart = (MIMEMultiPart) m_currentMessage; + int encoding = mime_translateMimeEncodingType( mi.m_value ); + mimeMultiPart.setContentEncoding( encoding ); + } + } + } + + else if ( MIMEHelper.bStringEquals( mi.m_name, "content-md5" ) ) + { + if ( m_nCurrentMessageType == MIME_UNINITIALIZED ) + m_mimeInfoQueue.addElement( mi ); + else + { + mimeBasicPart = (MIMEBasicPart) m_currentMessage; + mimeBasicPart.setContentMD5( new String( mi.m_value ) ); + + if ( m_dataSink != null ) + m_dataSink.contentMD5( mimeBasicPart.getUserObject(), mi.m_value ); + } + } + else + { + if (m_nCurrentMessageType == MIME_UNINITIALIZED) + m_mimeInfoQueue.addElement (mi); + else + { + // add unknown tags as header + addHeader( mi.m_name, mi.m_value, true ); +/* + mimeBasicPart = (MIMEBasicPart) m_currentMessage; + mimeBasicPart.setHeader (new String(mi.m_name), new String (mi.m_value)); + + if ( m_dataSink != null ) + m_dataSink.header (mimeBasicPart.getUserObject(), mi.m_name, mi.m_value); +*/ + } + } + } + + + + /** + * Parse a line into name, value, parameters and store it in a mimeInfo structure + * + * @author Carson Lee + * @version %I%, %G% + * + * @param mi mimeInfo structure to be parsed + * @return none + * @exception MIMEException + */ + private void parseMimeInfo( mimeInfo mi ) throws MIMEException + { + byte[] paramName = null; + byte[] value = null; + int offset = 0; + int len = 0; + boolean beginQuote = false; + byte ch; + int i; + mimeInfo mi2; + int nValueLen; + + if ( mi == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + nValueLen = mi.m_value.length; + + /* go through entire line */ + for ( i = 0; i < nValueLen; i++ ) + { + ch = mi.m_value[i]; + + /* handle quotes */ + if ( ch == '"' ) + beginQuote = beginQuote ? false : true; + + if ( beginQuote ) + { + len++; + continue; + } + + /* parse and fill in fields */ + switch ( ch ) + { + case ';': + value = MIMEHelper.byteSubstring( mi.m_value, offset, len ); + // trim(); + offset = i + 1; + + mi2 = new mimeInfo( MIME_INFO, paramName, value ); + + if ( mi2 == null ) + throw new MIMEException( MIMEHelper.szERROR_OUT_OF_MEMORY ); + + mi.m_param.addElement( mi2 ); + + value = null; + paramName = null; + len = 0; + break; + + case '=': + paramName = MIMEHelper.byteSubstring( mi.m_value, offset, len ); + // trim(); + offset = i + 1; + len = 0; + break; + + default: + len++; + } + } + + /* add it to internal buffer */ + if ( len > 0 ) + { + value = MIMEHelper.byteSubstring( mi.m_value, offset, len ); //trim(); + mi2 = new mimeInfo( MIME_INFO, paramName, value ); + + if ( mi2== null ) + throw new MIMEException( MIMEHelper.szERROR_OUT_OF_MEMORY ); + + mi.m_param.addElement( mi2 ); + } + } + +private byte[] getBytecontType (int nMessageType, int basicPartcontType) +{ + switch ( nMessageType ) + { + case MIMEMessage.BASICPART: + switch (basicPartcontType) + { + case MIMEBasicPart.TEXT: + return mb_TextPart; + case MIMEBasicPart.AUDIO: + return mb_AudioPart; + case MIMEBasicPart.VIDEO: + return mb_VideoPart; + case MIMEBasicPart.IMAGE: + return mb_ImagePart; + case MIMEBasicPart.APPLICATION: + return mb_ApplPart; + } + case MIMEMessage.MULTIPART: + return mb_MultiPart; + case MIMEMessage.MESSAGEPART: + return mb_MessagePart; + } + + return null; +} + + /** + * create new message structure based on content type + * the input string will be parsed to determine the content type + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input line + * @return none + * @exception MIMEException + */ + private void newMessageStructure( byte[] s ) throws MIMEException + { + MIMEMessagePart mimeMessagePart; + MIMEBasicPart mimeBasicPart; + MIMEMultiPart mimeMultiPart; + int nMessageType; + byte[] param[] = new byte[2][]; + int contentType[] = new int[1]; + + nMessageType = nGetMessageType( s, param, contentType ); + + switch ( nMessageType ) + { + case MIMEMessage.BASICPART: + mimeBasicPart = new MIMEBasicPart(); + + if ( m_dataSink != null ) + { + //if ( m_bLocalStorage ) + // mimeBasicPart.setUserObject( m_dataSink.startBasicPart( mimeBasicPart ) ); + //else + mimeBasicPart.setUserObject( m_dataSink.startBasicPart() ); + } + + addMessage( mimeBasicPart, MIMEMessage.BASICPART ); + break; + + case MIMEMessage.MULTIPART: + mimeMultiPart = new MIMEMultiPart(); + mimeMultiPart.m_parsedPart=1; + + if ( m_dataSink != null ) + { + //if ( m_bLocalStorage ) + // mimeMultiPart.setUserObject( m_dataSink.startMultiPart( mimeMultiPart ) ); + //else + mimeMultiPart.setUserObject( m_dataSink.startMultiPart() ); + } + + addMessage( mimeMultiPart, MIMEMessage.MULTIPART ); + + /* save boundary */ + mimeMultiPart = (MIMEMultiPart) m_currentMessage; + String boundary = parseForBoundary( new String(s) ); + mimeMultiPart.setBoundary( boundary ); + + // debug +// System.out.println( "boundary = [" + boundary + "]" ); + +// if ( m_dataSink != null ) +// m_dataSink.boundary( mimeMultiPart.getUserObject(), boundary.getBytes() ); + + break; + + case MIMEMessage.MESSAGEPART: + mimeMessagePart = new MIMEMessagePart(); + + if ( m_dataSink != null ) + { + //if ( m_bLocalStorage ) + // mimeMessagePart.setUserObject( m_dataSink.startMessagePart( mimeMessagePart ) ); + //else + mimeMessagePart.setUserObject( m_dataSink.startMessagePart() ); + + m_msgParts.addElement (mimeMessagePart); + + } + + if ( MIMEHelper.bStringEquals( param[0], "external-body" ) ) + { + m_messagePartSubType = SUBTYPE_EXTERNAL_BODY; + } + + // unsupported partial subtype + else if ( MIMEHelper.bStringEquals( param[0], "partial" ) ) + { + throw new MIMEException( MIMEHelper.szERROR_UNSUPPORTED_PARTIAL_SUBTYPE ); + } + + addMessage( mimeMessagePart, MIMEMessage.MESSAGEPART ); + + // determine subtype + if ( MIMEHelper.bStringEquals( param[0], "rfc822" ) ) + { + m_messagePartSubType = SUBTYPE_RFC822; + MIMEMessage m = new MIMEMessage(); + m.m_parsedPart = 1; + mimeMessagePart.setMessage( m, false ); + + if ( m_dataSink != null ) + { + //if (m_bLocalStorage) + // m.setUserObject (m_dataSink.startMessage (m)); + //else + m.setUserObject (m_dataSink.startMessage()); + } + + m_nextHeaderParent = m; + + // defered assignment till received first blank line + m_nextMimeMessage = m; + } + + break; + } + + // initialize structure + if ( m_currentMessage != null ) + { + MIMEBodyPart m = (MIMEBodyPart) m_currentMessage; + + if ( param[0] != null ) + m.setContentSubType( new String( param[0] ) ); + + if ( param[1] != null && !MIMEHelper.bStringEquals( param[1], "boundary")) + { + String cps = new String (param[1]); + + if (! (cps.indexOf ("boundary") > 0)) + m.setContentTypeParams( new String( param[1] ) ); + } + + if ( m_dataSink != null ) + { + //m_dataSink.contentType( m.getUserObject(), contentType[0] ); + m_dataSink.contentType (m.getUserObject(), getBytecontType (nMessageType, contentType[0])); + + if ( param[0] != null ) + m_dataSink.contentSubType( m.getUserObject(), param[0] ); + + if (param[1] != null && param[1].length > 0 && + !MIMEHelper.bStringEqualsLtrim(param[1], "boundary")) + { + m_dataSink.contentTypeParams( m.getUserObject(), param[1] ); + } + } + + if ( m_nCurrentMessageType == MIMEMessage.BASICPART ) + { + mimeBasicPart = (MIMEBasicPart) m_currentMessage; + mimeBasicPart.setContentType( contentType[0] ); + } + } + } + + + + + /** + * add a new message ( part ) to a multipart message, mimeMessage of messagePart + * + * @author Carson Lee + * @version %I%, %G% + * + * @param message input message + * @param nMessageType message type + * @return none + * @exception MIMEException + */ + private void addMessage( Object message, int nMessageType ) throws MIMEException + { + MIMEMultiPart multiPart = null; + MIMEBasicPart basicPart = null; + MIMEMessagePart messagePart = null; + int nCurrentParentType = getCurrentParentType(); + + if ( message == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + // determine current message + if ( nCurrentParentType == MIMEMessage.BASICPART ) + { + // can't add messages to a basicpart + return; + } + + else if ( nCurrentParentType == MIME_UNINITIALIZED ) + { + if ( nMessageType == MIMEMessage.BASICPART ) + { +//debug +//System.out.println( "adding basicpart to message" ); + + m_mimeMessage.setBody( (MIMEBasicPart) message, false ); + m_nMessageType = MIMEMessage.BASICPART; + } + + else if ( nMessageType == MIMEMessage.MULTIPART ) + { +//debug +//System.out.println( "adding multipart to message" ); + + m_mimeMessage.setBody( (MIMEMultiPart) message, false ); + m_nMessageType = MIMEMessage.MULTIPART; + m_currentParent.addElement( (MIMEMultiPart) message ); + m_fSeenBoundary = false; + } + + else if ( nMessageType == MIMEMessage.MESSAGEPART ) + { +//debug +//System.out.println( "adding messagepart to message" ); + + m_mimeMessage.setBody( (MIMEMessagePart) message, false ); + m_nMessageType = MIMEMessage.MESSAGEPART; + m_currentParent.addElement( (MIMEMessagePart) message ); + } + + setCurrentMessage( message, nMessageType ); + } + + // add to vector + else if ( nCurrentParentType == MIMEMessage.MULTIPART && m_currentMessage != null ) + { + multiPart = (MIMEMultiPart) m_currentParent.lastElement(); + + if ( nMessageType == MIMEMessage.BASICPART ) + { +//debug +//System.out.println( "adding basicpart to multipart" ); + + multiPart.addBodyPart( (MIMEBasicPart) message, false ); + } + + else if ( nMessageType == MIMEMessage.MULTIPART ) + { +//debug +//System.out.println( "adding multipart to multipart" ); + + multiPart.addBodyPart( (MIMEMultiPart) message, false ); + m_currentParent.addElement( (MIMEMultiPart) message ); + } + + else if ( nMessageType == MIMEMessage.MESSAGEPART ) + { +//debug +//System.out.println( "adding messagepart to multipart" ); + + multiPart.addBodyPart( (MIMEMessagePart) message, false ); + m_currentParent.addElement( (MIMEMessagePart) message ); + } + + setCurrentMessage( message, nMessageType ); + } + + else if ( nCurrentParentType == MIMEMessage.MESSAGEPART && m_currentMessage != null ) + { + messagePart = (MIMEMessagePart) m_currentParent.lastElement(); + + if ( nMessageType == MIMEMessage.BASICPART ) + { +//debug +//System.out.println( "adding basicpart to messagepart" ); + + messagePart.getMessage( false ).setBody( (MIMEBasicPart) message, false ); + } + + else if ( nMessageType == MIMEMessage.MULTIPART ) + { +//debug +//System.out.println( "adding multipart to messagepart" ); + + messagePart.getMessage( false ).setBody( (MIMEMultiPart) message, false ); + m_currentParent.addElement( (MIMEMultiPart) message ); + } + + else if ( nMessageType == MIMEMessage.MESSAGEPART ) + { +//debug +//System.out.println( "adding messagepart to messagepart" ); + + messagePart.getMessage( false ).setBody( (MIMEMessagePart) message, false ); + m_currentParent.addElement( (MIMEMessagePart) message ); + } + + setCurrentMessage( message, nMessageType ); + } + } + + + + + // helper functions ---------------------------------------- + + + + + /** + * Set current mesage + * + * @author Carson Lee + * @version %I%, %G% + * + * @param message input message + * @param nMessageType message type + * @return MIME_OK if successful, an error code otherwise + * @exception none + */ + private int setCurrentMessage( Object message, int nMessageType ) + { + if ( message == null ) + return NSMAIL_ERR_INVALIDPARAM; + + m_currentMessage = message; + m_nCurrentMessageType = nMessageType; + m_nextHeaderParent = message; + + if ( nMessageType == MIMEMessage.BASICPART ) + { + m_currentBasicPart = (MIMEBasicPart) message; + m_currentMultiPart = null; + m_currentMessagePart = null; + + int nCurrentParentType = getCurrentParentType(); + + if (nCurrentParentType == MIMEMessage.MULTIPART && m_fSeenBoundary) + { + //start following adding headers to this part itself + m_headerParent = message; + m_nextHeaderParent = message; + } + } + + else if ( nMessageType == MIMEMessage.MULTIPART ) + { + m_currentBasicPart = null; + m_currentMultiPart = (MIMEMultiPart) message; + m_currentMessagePart = null; + } + + else if ( nMessageType == MIMEMessage.MESSAGEPART ) + { + m_currentBasicPart = null; + m_currentMultiPart = null; + m_currentMessagePart = (MIMEMessagePart) message; + } + + return NSMAIL_OK; + } + + + /** + * Determine if line is a boundary + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input string + * @param len size of input string + * @return : START_BOUNDARY if it's a start boundary, END_BOUNDARY if it's a end boundary, NOT_A_BOUNDARY if it's not a boundary + * @exception none + */ + private int boundaryCheck( byte s[], int len ) + { + String currentBoundary = getCurrentBoundary(); + int boundaryLen = 0; + + if ( len > 0 && Character.isWhitespace( (char) s[len-1] ) ) + len--; + + if ( len > 0 && Character.isWhitespace( (char) s[len-1] ) ) + len--; + + if ( len >= 2 && s != null && currentBoundary != null ) + { + boundaryLen = currentBoundary.length(); + + if ( (len == boundaryLen + 4) && s[0] == '-' && s[1] == '-' && + s[ boundaryLen + 2 ] == '-' && s[ boundaryLen + 3 ] == '-' && + MIMEHelper.bStringEquals( 2, s, currentBoundary ) ) + { + return END_BOUNDARY; + } + else if ((len == boundaryLen + 2) && s[0] == '-' && s[1] == '-' && + MIMEHelper.bStringEquals( 2, s, currentBoundary ) ) + { + return START_BOUNDARY; + } + } + + return NOT_A_BOUNDARY; + } + + + + + + /** + * check what kind of line this is + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input string + * @param len size of input string + * @return line type can be MIME_INFO, MIME_XHEADER, MIME_CRLF, MIME_HEADER + * @exception none + */ + private int checkForLineType( byte s[], int len, boolean startData ) + { + if ( s == null ) + return NSMAIL_ERR_INVALIDPARAM; + + if ( startData ) + { + int type = boundaryCheck(s, len); + + if ( type != NOT_A_BOUNDARY ) + return type; + + return MIME_HEADER; + } + + else if ( MIMEHelper.bStringEquals( s, "content-" ) ) + return MIME_INFO; + + else if ( MIMEHelper.bStringEquals( s, "x-" ) ) + return MIME_XHEADER; + + else if ( len == 0 ) + return MIME_CRLF; + + else + { + int type = boundaryCheck(s, len); + + if ( type != NOT_A_BOUNDARY ) + return type; + else + { + boolean seenNonSpace = false; + + for ( int i = 0; i < len; i++ ) + { + if (!Character.isWhitespace ((char) s[i])) + seenNonSpace = true; + else if (seenNonSpace && Character.isWhitespace ((char) s[i])) // its not a header! Its MESSAGE_DATA! + { + break; + } + + if ( s[i] == ':' ) + return MIME_HEADER; + } + } + } + + return MIME_MESSAGE_DATA; + } + + + + + /** + * Get message type, subtype, content type parameters and content type + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input string + * @param param[] (output) returns subtype in param[0], parameters in param[1] + * @param contentType[] (output) returns content type in contentType[0] + * @return message type, returns subtype in param[0], parameters in param[1] + * @exception none + */ + private int nGetMessageType( byte[] s, byte[] param[], int contentType[] ) throws MIMEException + { + int i, nStart, nSubtypeStart; + byte[] MessageType = null; + int nMessageType = MIME_UNINITIALIZED; + byte[] subtype = null; + + if ( s == null || param == null ) + return NSMAIL_ERR_INVALIDPARAM; + + int len = s.length; + + /* skip spaces and colons */ + for ( i=0; i < len && s[i] != 0 && ( Character.isWhitespace( (char)s[i] ) || s[i] == ':' ); i++ ) + ; + + /* skip to subtype */ + for ( nStart = i; i < len && s[i] != 0 && s[i] != '/' && s[i] != ';'; i++ ) + ; + + /* content type */ + MessageType = MIMEHelper.byteSubstring( s, nStart, i - nStart ); + + /* content subtype */ + if ( i < len && s[i] == '/' ) + { + for ( nSubtypeStart = ++i; i < len && s[i] != 0 && s[i] != ';' && s[i] != ','; i++ ) + ; + + /* content subtype */ + subtype = MIMEHelper.byteSubstring( s, nSubtypeStart, i - nSubtypeStart ); + } + + param[0] = subtype; + + /* skip spaces and colons */ + if ( i < len ) + { + for ( ; i < len && s[i] != 0 && ( s[i] == ' ' || s[i] == ';' || s[i] == ',' ); i++ ) + ; + } + + param[1] = i > (int) s.length ? null : MIMEHelper.byteSubstring( s, i, s.length-i ); // trim + + /* determine content type */ + if ( MIMEHelper.bStringEquals( MessageType, "text" ) ) + { + nMessageType = MIMEMessage.BASICPART; + contentType[0] = MIMEBasicPart.TEXT; + } + + else if ( MIMEHelper.bStringEquals( MessageType, "audio" ) ) + { + nMessageType = MIMEMessage.BASICPART; + contentType[0] = MIMEBasicPart.AUDIO; + } + + else if ( MIMEHelper.bStringEquals( MessageType, "image" ) ) + { + nMessageType = MIMEMessage.BASICPART; + contentType[0] = MIMEBasicPart.IMAGE; + } + + else if ( MIMEHelper.bStringEquals( MessageType, "video" ) ) + { + nMessageType = MIMEMessage.BASICPART; + contentType[0] = MIMEBasicPart.VIDEO; + } + + else if ( MIMEHelper.bStringEquals( MessageType, "application" ) ) + { + nMessageType = MIMEMessage.BASICPART; + contentType[0] = MIMEBasicPart.APPLICATION; + } + + else if ( MIMEHelper.bStringEquals( MessageType, "multipart" ) ) + { + nMessageType = MIMEMessage.MULTIPART; + contentType[0] = MIMEBasicPart.TEXT; + } + + else if ( MIMEHelper.bStringEquals( MessageType, "message" ) ) + { + nMessageType = MIMEMessage.MESSAGEPART; + contentType[0] = MIMEBasicPart.TEXT; + } + + else + { + //nMessageType = MIMEBasicPart.TEXT; + //contentType[0] = MIMEBasicPart.TEXT; + throw new MIMEException ("Error: Unknown/Unsupported Content-Type: " + new String (MessageType)); + } + + return nMessageType; + } + + + + + /** + * Parse an input string for boundary + * default to "-----" if none is found + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input string + * @return boundary string + * @exception none + */ + private String parseForBoundary( String s ) + { + String boundary; + int nLen; + int len; + int start; + int pos; + boolean bQuotes = false; + char ch; + + if ( s == null ) + return null; + + pos = s.indexOf( "boundary" ); + len = s.length(); + + if ( pos == -1 ) + boundary = "-----"; + + else + { + /* skip spaces and colons */ + for ( pos = pos + 9, ch = s.charAt(pos); pos < len && ( ch == ' ' || ch == '=' ); ch = s.charAt( ++pos >= len ? 0 : pos ) ) + ; + + if ( ch == '"' ) + { + bQuotes = true; + pos++; + } + + start = pos; + + for ( nLen = 0, ch = s.charAt(pos); pos < len && ch != ';'; nLen++, ch = s.charAt( ++pos >= len ? 0 : pos ) ) + { + if ( bQuotes && ch == '"' ) + break; + } + + boundary = s.substring( start, start + nLen ); + } + + return boundary; + } + + + + + /** + * translate encoding type to an integer + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input string + * @return encoding type + * @exception none + */ + private int mime_translateMimeEncodingType( byte[] s ) + { + if ( s == null ) + return NSMAIL_ERR_INVALIDPARAM; + + if ( MIMEHelper.bStringEquals( s, "base64" ) ) + return MIMEBodyPart.BASE64; + + else if ( MIMEHelper.bStringEquals( s, "qp" ) || MIMEHelper.bStringEquals( s, "quoted-printable" ) ) + return MIMEBodyPart.QP; + + else if ( MIMEHelper.bStringEquals( s, "7bit" ) ) + return MIMEBodyPart.E7BIT; + + else if ( MIMEHelper.bStringEquals( s, "8bit" ) ) + return MIMEBodyPart.E8BIT; + + else if ( MIMEHelper.bStringEquals( s, "binary" ) ) + return MIMEBodyPart.BINARY; + + return MIMEBodyPart.E7BIT; + } + + + + + /** + * translate disposition type to an integer + * + * @author Carson Lee + * @version %I%, %G% + * + * @param s input string + * @param param[] return content disposition parameters in param[0] + * @return
+    *   NSMAIL_ERR_INVALIDPARAM     if parameters are invalid
+    *   MIMEBodyPart.INLINE         disposition type is inline
+    *   MIMEBodyPart.ATTACHMENT     disposition type is an attachment
+ * + * @exception MIMEException + */ + private int translateDispType( byte[] s, byte[] param[] ) + { + if ( s == null || param == null ) + return NSMAIL_ERR_INVALIDPARAM; + + param[0] = null; + + if ( MIMEHelper.bStringEquals( s, "inline" ) ) + { + if ( s.length >= 8 ) + param[0] = MIMEHelper.byteSubstring( s, 0, 7 ); + + return MIMEBodyPart.INLINE; + } + + else if ( MIMEHelper.bStringEquals( s, "attachment" ) ) + { + if ( s.length >= 12 ) + param[0] = MIMEHelper.byteSubstring( s, 0, 12 ); + + return MIMEBodyPart.ATTACHMENT; + } + + else if ( s.length >= 8 ) + param[0] = MIMEHelper.byteSubstring( s, 0, 7 ); + + return MIMEBodyPart.INLINE; + } + + // ----------new one + private int translateDispType (byte[] s) + { + if ( s == null) + return NSMAIL_ERR_INVALIDPARAM; + + if (MIMEHelper.bStringEquals (s, "inline")) + return MIMEBodyPart.INLINE; + + if (MIMEHelper.bStringEquals (s, "attachment")) + return MIMEBodyPart.ATTACHMENT; + + return MIMEBodyPart.INLINE; + } + + private int getCurrentParentType() + { + if ( m_currentParent.size() > 0 ) + { + Object o = m_currentParent.lastElement(); + + if ( o != null ) + { + if ( o instanceof MIMEBasicPart ) + return MIMEMessage.BASICPART; + + else if ( o instanceof MIMEMultiPart ) + return MIMEMessage.MULTIPART; + + else if ( o instanceof MIMEMessagePart ) + return MIMEMessage.MESSAGEPART; + } + } + + return MIME_UNINITIALIZED; + } + + + + /** + * get the boundary corresponding to the current multiPart message + * + * @author Carson Lee + * @version %I%, %G% + * + * @param none + * @return current boundary string, null if there isn't a current boundary + * @exception none + */ + private String getCurrentBoundary() + { + if ( m_currentParent.size() > 0 ) + { + for ( int i = m_currentParent.size(); i > 0; i-- ) + { + Object o = m_currentParent.elementAt( i - 1 ); + + if ( o != null && o instanceof MIMEMultiPart ) + return ( (MIMEMultiPart) o ).getBoundary(); + } + } + + return null; + } + + + + + /** + * get the boundary corresponding to the current multiPart message + * + * @author Carson Lee + * @version %I%, %G% + * + * @param none + * @return current boundary string, null if there isn't a current boundary + * @exception none + */ + private void unwindCurrentParent( byte[] s, boolean delete ) + { + if ( m_currentParent.size() > 0 ) + { + String boundary = new String( s, 2, s.length - 2 ); + + for ( int i = m_currentParent.size(); i > 0; i-- ) + { + Object o = m_currentParent.elementAt( i - 1 ); + + if ( o != null && o instanceof MIMEMultiPart && boundary.startsWith( ((MIMEMultiPart) o ).getBoundary() ) ) + { + if ( delete ) + { + m_currentParent.removeElementAt( i - 1 ); + + // reposition to next multipart + for ( ; m_currentParent.size() > 0; ) + { + o = m_currentParent.lastElement(); + + if ( o instanceof MIMEMultiPart ) + return; + + m_currentParent.removeElementAt( m_currentParent.size() - 1 ); + } + } + + return; + } + + m_currentParent.removeElementAt( i - 1 ); + } + + } + } + + + + + /** + * check for empty messages in all basicparts + * + * @author Carson Lee + * @version %I%, %G% + * + * @param + * + * o : any mime message structure, usually MIMEMessage + * + * @return none + * @exception MIMEException + */ + void checkForEmptyMessages( Object o ) throws MIMEException + { + if ( o == null ) + throw new MIMEException( MIMEHelper.szERROR_BAD_PARAMETER ); + + if ( o instanceof MIMEMessage ) + { + checkForEmptyMessages( ((MIMEMessage) o).getBody() ); + } + + else if ( o instanceof MIMEBasicPart ) + { + if ( ((MIMEBasicPart) o).getMessageDataLen() == 0 ) + throw new MIMEException( MIMEHelper.szERROR_EMPTY_MESSAGE ); + } + + else if ( o instanceof MIMEMultiPart ) + { + MIMEMultiPart m = (MIMEMultiPart) o; + int count = m.getBodyPartCount(); + + for ( int i = 0; i < count; i++ ) + { + Object part = m.getBodyPart( i, false ); + checkForEmptyMessages( part ); + } + } + + else if ( o instanceof MIMEMessagePart ) + { + String cst = (((MIMEMessagePart)o).getContentSubType()).trim(); + + if (cst == null || cst.equalsIgnoreCase("rfc822")) + { + MIMEMessage messagePart = ((MIMEMessagePart) o).getMessage(); + checkForEmptyMessages( messagePart ); + } + else if (cst.equalsIgnoreCase("external-body")) + { + Header[] hdrs = ((MIMEMessagePart)o).getAllHeaders(); + //if (hdrs == null) + // throw new MIMEException (MIMEHelper.szERROR_BAD_EXTERNAL_MESSAGE_PART); + } + else if (cst.equalsIgnoreCase("partial")) + { + throw new MIMEException (MIMEHelper.szERROR_UNSUPPORTED_PARTIAL_SUBTYPE); + } + else + throw new MIMEException( MIMEHelper.szERROR_BAD_MIME_MESSAGE); + } + } +} + diff --git a/msgsdk/java/protocol/MIME/src/Makefile b/msgsdk/java/protocol/MIME/src/Makefile new file mode 100644 index 00000000000..d27124a8374 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/Makefile @@ -0,0 +1,73 @@ +########################################################################## +# MIME makefile. +# created 9/26/97 -- Prasad +# +############################################################################ +# environment +#SHELL = /usr/bin/ksh + +# commands +JAVAC = javac +ARCH = $(shell uname -s) + +ifeq ($(ARCH), SunOS) + ARCH = SOLARIS +endif + +ifeq ($(ARCH), HP-UX) + ARCH = HPUX +endif + +# java flags +DEBUGJAVAFLAG = +OPTJAVAFLAG = -d $(CLASSDIR) +JAVAFLAGS = $(OTHERJAVAFLAGS) $(OPTJAVAFLAG) $(DEBUGJAVAFLAG) +RM = rm -f + +# files and directories +#CLASSDIR = ./built +CLASSDIR = ../../../built/$(ARCH)/protocol + +#CLASSPATH = .:$(CLASSDIR):$(JDKCLASSPATH) + +SRCS = \ + Header.java \ + fileMIMEType.java \ + MIMEHelper.java \ + MIMEException.java \ + MIMEBodyPart.java \ + MIMEBasicPart.java \ + MIMEMessage.java \ + MIMEMessagePart.java \ + MIMEMultiPart.java \ + MIMEDataSink.java \ + MIMEParser.java + +OBJS = ${SRCS:.java=.class} + +TARGET = package + +.SUFFIXES: .java .class + +all: $(CLASSDIR) $(TARGET) + +install: $(TARGET) + foreach f ( $(OBJS) ) \ + mv -f $$f $(CLASSDIR)/$$f \ + end + +#$(TARGET): $(OBJS) +$(TARGET): + $(JAVAC) $(JAVAFLAGS) *.java +$(CLASSDIR): + echo mkdir $(CLASSDIR) + - mkdir -p $(CLASSDIR) + +#$(OBJS): +# $(JAVAC) $(JAVAFLAGS) *.java +#.java.class: $(SRCS) +# $(JAVAC) $(JAVAFLAGS) $< + +clean: + rm -f $(CLASSDIR)/netscape/messaging/mime/*.class + rm -f $(CLASSDIR)/netscape/mime/*.class diff --git a/msgsdk/java/protocol/MIME/src/fileMIMEType.java b/msgsdk/java/protocol/MIME/src/fileMIMEType.java new file mode 100644 index 00000000000..a1988355ef2 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/fileMIMEType.java @@ -0,0 +1,54 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +package netscape.messaging.mime; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * The fileMIMEType class contains + * File MIME Content type and MIME encoding information. + * @author Prasad Yendluri + */ +public class fileMIMEType +{ + public int content_type; + public String content_subtype; + public String content_params; + public String file_extn; + public String file_shortname; + public int mime_encoding = MIMEBodyPart.BASE64; + + /** + * Creates a fileMIMEType object. + * Default constructor for the fileMIMEType class. + */ + public fileMIMEType () + { + content_type = 0; + } +} diff --git a/msgsdk/java/protocol/MIME/src/log.java b/msgsdk/java/protocol/MIME/src/log.java new file mode 100644 index 00000000000..4463b341de8 --- /dev/null +++ b/msgsdk/java/protocol/MIME/src/log.java @@ -0,0 +1,119 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +// log.java +// Carson Lee +// Log class +// + +package netscape.messaging.mime; + +//import java.io.*; +import java.io.FileWriter; +import java.io.BufferedWriter; +import java.util.Date; +import java.io.IOException; + + +public class log +{ + // defines + final static String errorLogFilename = "nsmail.log"; + final static String spaces = " "; + static boolean bLogOn = true; + static boolean bTimestampOn = true; + static BufferedWriter errorLog; + + public static void init() + { + try + { + if ( errorLog == null ) + errorLog = new BufferedWriter( new FileWriter( errorLogFilename, true ), 1024 ); + } + catch ( IOException e ) + { + } + } + + + public static void close() + { + try + { + if ( errorLog != null ) + { + errorLog.flush(); + errorLog.close(); + errorLog = null; + } + } + catch ( IOException e ) + { + } + } + + + protected void finalize() + { + close(); + } + + + public static void errorLog( String message ) + { + if ( !bLogOn || message == null ) + return; + + if ( errorLog == null ) + init(); + + try + { + // option to turn off timestamp, it's expensive to use + if ( bTimestampOn ) + { + Date timeStamp = new Date(); + timeStamp.setTime( System.currentTimeMillis() ); + errorLog.write( timeStamp.toString(), 0, 28 ); + errorLog.write( spaces, 0, 6 ); + } + + errorLog.write( message, 0, message.length() ); + errorLog.newLine(); + } + catch ( IOException e ) + { + } + } + + + public static void timestampOn() { bTimestampOn = true; } + public static void timestampOff() { bTimestampOn = false; } + public static void LogOn() { bLogOn = true; } + public static void LogOff() { bLogOn = false; } + + +} // eof log.java diff --git a/msgsdk/java/protocol/MIME/testapp/ReadMe.txt b/msgsdk/java/protocol/MIME/testapp/ReadMe.txt new file mode 100644 index 00000000000..93d5e4ff44a --- /dev/null +++ b/msgsdk/java/protocol/MIME/testapp/ReadMe.txt @@ -0,0 +1,32 @@ +This directory contains an example program that demonstrates the use of the Netscape +Messaging MIME Parser java API. + +To compile and run: +------------------- + +(1) Make sure CLASSPATH includes the msg-sdk jar file (proapi.jar). + When installed proapi.jar is located at /packages/proapi.jar. + Make sure CLASSPATH also includes the directory you are compiling in. + +(2) Compile: javac *.java +(3) Run: java testApp + + This shows the usage: + + usage: java testApp [D] + + Explanation of parameters above: + : file with MIME message to parse + [D] For dynamic parsing (default). + Any other values performs static parsing + +(4) Run again with appropriate parameters. For example: + + java testApp mime1.txt D + +NOTE: The other two program testAppStr.java and + testAppStrLoop.java have minor differences with + testApp.java. + + testAppStr.java passes the entire input stream to the parser in one shot. + testAppStrLoop.java Demonstrate parsing > 1 messages with the same parser object. diff --git a/msgsdk/java/protocol/MIME/testapp/myDataSink.java b/msgsdk/java/protocol/MIME/testapp/myDataSink.java new file mode 100644 index 00000000000..68210bdb96d --- /dev/null +++ b/msgsdk/java/protocol/MIME/testapp/myDataSink.java @@ -0,0 +1,287 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +import java.io.*; +import java.lang.*; +import java.util.*; +import netscape.messaging.mime.*; + +public class myDataSink extends MIMEDataSink +{ + FileOutputStream out; + FileOutputStream out2; + int m_nChunkNo; + String contentType; + int basicPartCount, msgPartCount, msgCount, multiPartCount; + + public myDataSink() + { + super(); + + try + { + out = new FileOutputStream ("output.log"); + out2 = new FileOutputStream ("list2.com"); + } + catch (IOException e) + { + // Ignore if we can't create a log file + } + + m_nChunkNo = 0; + msgCount=0; + basicPartCount=0; + msgPartCount=0; + multiPartCount=0; + } + + public void log (String s) + { + System.out.println (s); + String ss = s + "\r\n"; + + if (out != null) + try + { + out.write (ss.getBytes()); + } + catch (IOException e) + { + // Ignore if we can't write to log file + } + } + + + public void header (Object callbackObject, byte[] name, byte[] value) + { + String s; + + if (callbackObject != null) + { + s = (String) callbackObject; + + log (s + "> header() --> name = [" + new String(name) + "] value = [" + + new String (value) + "]"); + } + else + { + log ("header() --> name = [" + new String(name) + "] value = [" + + new String (value) + "]"); + } + } + + public void addHeader (Object callbackObject, byte[] name, byte[] value) + { + String s; + + if (callbackObject != null) + { + s = (String) callbackObject; + + log (s + "> addHeader() --> name = [" + new String(name) + "] value = [" + + new String (value) + "]"); + } + else + { + log ("addHeader() --> name = [" + new String(name) + "] value = [" + + new String (value) + "]"); + } + } + + + public void endMessageHeader (Object callbackObject) + { + String s; + + if (callbackObject != null) + { + s = (String) callbackObject; + + log (s + " endMessageHeader()"); + } + else + { + log ("???? endMessageHeader()"); + } + } + + public void contentType (Object callbackObject, byte[] bContentType) + { + contentType = new String (bContentType); + String s = (String) callbackObject; + log (s + "> contentType() --> [" + contentType + "]"); + } + + public void contentSubType (Object callbackObject, byte[] contentSubType) + { + String s = (String) callbackObject; + log (s + "> contentSubType() --> [" + new String (contentSubType) + "]"); + } + + public void contentTypeParams (Object callbackObject, byte[] contentTypeParams) + { + String s = (String) callbackObject; + log (s+ "> contentTypeParams() --> [" + new String (contentTypeParams) + "]"); + } + + public void contentID (Object callbackObject, byte[] contentID) + { + String s = (String) callbackObject; + log (s+ "> contentID() --> [" + new String (contentID) + "]"); + } + + public void contentMD5 (Object callbackObject, byte[] contentMD5) + { + String s = (String) callbackObject; + log (s+ "> contentMD5() --> + [" + new String (contentMD5) + "]"); + } + + public void contentDisposition (Object callbackObject, int nContentDisposition) + { + String s = (String) callbackObject; + log (s+ "> contentDisposition() --> [" + nContentDisposition + "]"); + } + + public void contentDispParams (Object callbackObject, byte[] contentDispParams) + { + String s = (String) callbackObject; + log (s+ "> contentDispParams() --> [" + new String (contentDispParams) + "]"); + } + + public void contentDescription (Object callbackObject, byte[] contentDescription) + { + String s = (String) callbackObject; + log (s+ "> contentDescription() --> [" + new String (contentDescription) + "]"); + } + + public void contentEncoding (Object callbackObject, int nContentEncoding) + { + String s = (String) callbackObject; + log (s+ "> contentEncoding() --> [" + nContentEncoding + "]"); + } + + public Object startMessage () + { + String s = new String ("message" + ++msgCount); + + log ("startMessage() " + msgCount); + + return s; + //return null; + } + + public void endMessage (Object callbackObject) + { + String s = (String) callbackObject; + log (s+ "> endMessage()"); + + try + { + if (out != null) + out.close(); + if (out2 != null) + out2.close(); + } + catch (IOException e) + { + } + } + + public Object startBasicPart () + { + String s = new String ("BasicPart" + ++basicPartCount); + log ("startBasicPart() " + basicPartCount); + return s; + //return null; + } + + public void bodyData (Object callbackObject, InputStream input, int len) + { + String s = (String) callbackObject; + + log (s+ "> bodyData() --> len = " + len + " ChunkNo = " + m_nChunkNo++); + + if (len == 0) + return; + + //if (contentType.equalsIgnoreCase ("Application")) + { + try + { + for (int b = input.read(), i = 0; i < len && b != -1; b = input.read(), i++) + out2.write (b); + } + catch (IOException e) + { + } + } + } + + public void endBasicPart (Object callbackObject) + { + String s = (String) callbackObject; + + log (s+ "> endBasicPart()"); + } + + public Object startMultiPart () + { + String s = new String ("MultiPart" + ++multiPartCount); + log ("startMultiPart() " + multiPartCount); + + return s; + //return null; + } + + public void boundary (Object callbackObject, byte[] boundary) + { + String s = (String) callbackObject; + log (s+ "> boundary() --> [" + new String (boundary) + "]"); + } + + public void endMultiPart (Object callbackObject) + { + String s = (String) callbackObject; + + log (s+ "> endMultiPart()"); + } + + public Object startMessagePart () + { + String s = new String ("MessagePart" + ++msgPartCount); + + log ("startMessagePart() " + msgPartCount); + + return s; + //return null; + } + + public void endMessagePart (Object callbackObject) + { + String s = (String) callbackObject; + log (s+ "> endMessagePart()"); + } + +} diff --git a/msgsdk/java/protocol/MIME/testapp/testApp.java b/msgsdk/java/protocol/MIME/testapp/testApp.java new file mode 100644 index 00000000000..5d1f37f659d --- /dev/null +++ b/msgsdk/java/protocol/MIME/testapp/testApp.java @@ -0,0 +1,368 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +/* + * This example programs demonstrates the use of Static and + * Dynamic MIME Parser. + */ + +import java.awt.*; +import java.sql.*; +import java.util.Vector; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.IOException; +import netscape.messaging.mime.*; + + +public class testApp +{ + byte b[]; + FileOutputStream logfile; + int m_nFileNo; + + public testApp() + { + b = new byte[100000]; + m_nFileNo=0; + + try + { + logfile = new FileOutputStream ("testapp.log"); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + void showObject (Object o) + { + InputStream in = null; + + if (o == null) + return; + + if (o instanceof MIMEMessage) + { + output ("--------------- Start Mime Message ---------------\n"); + + MIMEMessage m = (MIMEMessage) o; + + try + { + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("message header = [" + h[i].getLine()); + } + + output ("* message body type = " + m.getBodyType() + "\n"); + output ("* message content type = " + m.getContentType() + "\n"); + output ("* message content subtype = " + m.getContentSubType() + "\n"); + output ("* message content type param = " + m.getContentTypeParams() + "\n"); + + showObject (m.getBody(false)); + output ("---------------- End Mime Message -----------------\n"); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + } + else if (o instanceof MIMEBasicPart) + { + output ("------------------- Start Mime BasicPart -------------------\n"); + + try + { + MIMEBasicPart m = (MIMEBasicPart) o; + in = m.getBodyData(); + + if (in != null) + { + int len = 0; + + if (m.getMessageDataLen() < 100000) + len = in.read (b); + + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("basic header = [" + h[i].getLine() ); + } + + output ("* basicpart content type = " + m.getContentType() + "\n"); + output ("* basicpart content subtype = " + m.getContentSubType() + "\n"); + output ("* basicpart content type param = " + m.getContentTypeParams() + "\n"); + output ("* basicpart content ID = " + m.getContentID() + "\n"); + output ("* basicpart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* basicpart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* basicpart content Description = " + m.getContentDescription() + "\n"); + output ("* basicpart content MD5 = " + m.getContentMD5() + "\n"); + output ("* basicpart getbodysize() = " + m.getSize() + "\n"); + output ("* basicpart content encoding = " + m.getContentEncoding() + "\n"); + + output (">>>>>>>>>>>>>>>>>>>> start data >>>>>>>>>>>>>>>>>>>\n"); + + // base64 + if (m.getContentEncoding() == 0) + output ("[BASE64 BINARY DATA]"); + // QP + else if (m.getContentEncoding() == 1) + output ("[QP DATA]"); + // text + else + output (b, len); + + output ("\n>>>>>>>>>>>>>>>>>>>> end data >>>>>>>>>>>>>>>>>>>>\n"); + + // base64 + if (m.getContentEncoding() == 1 && m.getMessageDataLen() < 100000) + { + FileOutputStream out = new FileOutputStream ("bodydata" + m_nFileNo++ + ".out"); + out.write (b, 0, m.getMessageDataLen()); + out.close(); + } + + in.close(); + } + } + catch (MIMEException e) + { + output (e.getMessage()); + } + catch (IOException e) + { + output (e.getMessage()); + } + + output ("------------------- End Mime BasicPart -------------------\n"); + } + else if (o instanceof MIMEMultiPart) + { + output ("------------------- Start Mime MultiPart -------------------\n"); + + MIMEMultiPart m = (MIMEMultiPart) o; + int count = m.getBodyPartCount(); + + // debug + output ("* multipart content type = " + m.getContentType() + "\n"); + output ("* multipart content subtype = " + m.getContentSubType() + "\n"); + output ("* multipart content type param = " + m.getContentTypeParams() + "\n"); + output ("* multipart content ID = " + m.getContentID() + "\n"); + output ("* multipart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* multipart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* multipart content Description = " + m.getContentDescription() + "\n"); + + if (count > 0) + for (int i = 0; i < count; i++) + { + try + { + Object part = m.getBodyPart(i, false); + showObject (part); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + } + + output ("------------------- End Mime MultiPart ------------------\n"); + } + + else if (o instanceof MIMEMessagePart) + { + output ("------------------- Start Mime MessagePart ----------------\n"); + + MIMEMessagePart m = (MIMEMessagePart) o; + + try + { + MIMEMessage part = m.getMessage(false); + + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("messagepart header = [" + h[i].getLine() ); + } + + output ("* messagepart content type = " + m.getContentType() + "\n"); + output ("* messagepart content subtype = " + m.getContentSubType() + "\n"); + output ("* messagepart content type param = " + m.getContentTypeParams() + "\n"); + output ("* messagepart content ID = " + m.getContentID() + "\n"); + output ("* messagepart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* messagepart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* messagepart content Description = " + m.getContentDescription() + "\n"); + output ("* messagepart content encoding = " + m.getContentEncoding() + "\n"); + + showObject (part); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + + output ("------------------- End Mime MessagePart -----------------\n"); + } + } + + + void parseFileMessage (String filename, boolean bDynamic) + { + if (!bDynamic) // perform Static Parsing + { + MIMEParser p = new MIMEParser(); + + output ("============== Begin Static Parsing " + filename + " ============\n"); + + if (filename != null && p != null) + { + try + { + FileInputStream input = new FileInputStream (filename); + showObject (p.parseEntireMessage (input)); + input.close(); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + catch (MIMEException e) + { + System.out.println (e.getMessage()); + } + } + + output ("\n=========== Parsing Complete " + filename + " =============\n\n"); + + } + else // perform Dynamic Parsing + { + try + { + myDataSink dataSink = new myDataSink(); + MIMEDynamicParser p = new MIMEDynamicParser (dataSink); + FileInputStream input = new FileInputStream (filename); + byte[] buffer = new byte[256]; + int bytesRead; + + output ("============== Begin Dynamic Parsing " + filename + " ============\n"); + + p.beginParse(); + + bytesRead = input.read (buffer); + + while (bytesRead > 0) + { + p.parse (buffer); + bytesRead = input.read (buffer); + } + + p.endParse(); + input.close(); + + //showObject (dataSink.m_mimeMessage); + + output ("\n=========== Parsing Complete " + filename + " =============\n\n"); + } + catch (MIMEException e) + { + System.out.println (e.getMessage()); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + + } // bDynamic + + } // parseFileMessage + + + void output (String s) + { + try + { + logfile.write (s.getBytes()); + System.out.println (s); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + + void output (byte b[], int len) + { + try + { + logfile.write (b, 0, len); + System.out.println (new String (b, 0, len)); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + /************ MIAN ******************/ + static public void main(String args[]) + { + boolean bDynamic=true; // Dynamic Parsing by Default + String filename; + + if (args.length < 1) + { + System.out.println("usage: java testApp [D]"); + System.out.println("example: java testApp mime1.txt"); + System.exit(0); + } + + filename = args[0]; + + if (args.length > 1 && !args[1].equalsIgnoreCase("D")) + { + bDynamic = false; // Switch to Static Parsing + } + + log.init(); + log.timestampOff(); + testApp ta = new testApp(); + + // parse the message in the file + ta.parseFileMessage (filename, bDynamic); + } +} diff --git a/msgsdk/java/protocol/MIME/testapp/testAppStr.java b/msgsdk/java/protocol/MIME/testapp/testAppStr.java new file mode 100644 index 00000000000..e21441c242e --- /dev/null +++ b/msgsdk/java/protocol/MIME/testapp/testAppStr.java @@ -0,0 +1,370 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +/* + * This example programs demonstrates the use of Static and + * Dynamic MIME Parser. + */ + +import java.awt.*; +import java.sql.*; +import java.util.Vector; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.IOException; +import netscape.messaging.mime.*; + + +public class testAppStr +{ + byte b[]; + FileOutputStream logfile; + int m_nFileNo; + + public testAppStr() + { + b = new byte[100000]; + m_nFileNo=0; + + try + { + logfile = new FileOutputStream ("testapp.log"); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + void showObject (Object o) + { + InputStream in = null; + + if (o == null) + return; + + if (o instanceof MIMEMessage) + { + output ("--------------- Start Mime Message ---------------\n"); + + MIMEMessage m = (MIMEMessage) o; + + try + { + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("message header = [" + h[i].getLine()); + } + + output ("* message body type = " + m.getBodyType() + "\n"); + output ("* message content type = " + m.getContentType() + "\n"); + output ("* message content subtype = " + m.getContentSubType() + "\n"); + output ("* message content type param = " + m.getContentTypeParams() + "\n"); + + showObject (m.getBody(false)); + output ("---------------- End Mime Message -----------------\n"); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + } + else if (o instanceof MIMEBasicPart) + { + output ("------------------- Start Mime BasicPart -------------------\n"); + + try + { + MIMEBasicPart m = (MIMEBasicPart) o; + in = m.getBodyData(); + + if (in != null) + { + int len = 0; + + if (m.getMessageDataLen() < 100000) + len = in.read (b); + + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("basic header = [" + h[i].getLine() ); + } + + output ("* basicpart content type = " + m.getContentType() + "\n"); + output ("* basicpart content subtype = " + m.getContentSubType() + "\n"); + output ("* basicpart content type param = " + m.getContentTypeParams() + "\n"); + output ("* basicpart content ID = " + m.getContentID() + "\n"); + output ("* basicpart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* basicpart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* basicpart content Description = " + m.getContentDescription() + "\n"); + output ("* basicpart content MD5 = " + m.getContentMD5() + "\n"); + output ("* basicpart getbodysize() = " + m.getSize() + "\n"); + output ("* basicpart content encoding = " + m.getContentEncoding() + "\n"); + + output (">>>>>>>>>>>>>>>>>>>> start data >>>>>>>>>>>>>>>>>>>\n"); + + // base64 + if (m.getContentEncoding() == 0) + output ("[BASE64 BINARY DATA]"); + // QP + else if (m.getContentEncoding() == 1) + output ("[QP DATA]"); + // text + else + output (b, len); + + output ("\n>>>>>>>>>>>>>>>>>>>> end data >>>>>>>>>>>>>>>>>>>>\n"); + + // base64 + if (m.getContentEncoding() == 1 && m.getMessageDataLen() < 100000) + { + FileOutputStream out = new FileOutputStream ("bodydata" + m_nFileNo++ + ".out"); + out.write (b, 0, m.getMessageDataLen()); + out.close(); + } + + in.close(); + } + } + catch (MIMEException e) + { + output (e.getMessage()); + } + catch (IOException e) + { + output (e.getMessage()); + } + + output ("------------------- End Mime BasicPart -------------------\n"); + } + else if (o instanceof MIMEMultiPart) + { + output ("------------------- Start Mime MultiPart -------------------\n"); + + MIMEMultiPart m = (MIMEMultiPart) o; + int count = m.getBodyPartCount(); + + // debug + output ("* multipart content type = " + m.getContentType() + "\n"); + output ("* multipart content subtype = " + m.getContentSubType() + "\n"); + output ("* multipart content type param = " + m.getContentTypeParams() + "\n"); + output ("* multipart content ID = " + m.getContentID() + "\n"); + output ("* multipart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* multipart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* multipart content Description = " + m.getContentDescription() + "\n"); + + if (count > 0) + for (int i = 0; i < count; i++) + { + try + { + Object part = m.getBodyPart(i, false); + showObject (part); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + } + + output ("------------------- End Mime MultiPart ------------------\n"); + } + + else if (o instanceof MIMEMessagePart) + { + output ("------------------- Start Mime MessagePart ----------------\n"); + + MIMEMessagePart m = (MIMEMessagePart) o; + + try + { + MIMEMessage part = m.getMessage(false); + + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("messagepart header = [" + h[i].getLine() ); + } + + output ("* messagepart content type = " + m.getContentType() + "\n"); + output ("* messagepart content subtype = " + m.getContentSubType() + "\n"); + output ("* messagepart content type param = " + m.getContentTypeParams() + "\n"); + output ("* messagepart content ID = " + m.getContentID() + "\n"); + output ("* messagepart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* messagepart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* messagepart content Description = " + m.getContentDescription() + "\n"); + output ("* messagepart content encoding = " + m.getContentEncoding() + "\n"); + + showObject (part); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + + output ("------------------- End Mime MessagePart -----------------\n"); + } + } + + + void parseFileMessage (String filename, boolean bDynamic) + { + if (!bDynamic) // perform Static Parsing + { + MIMEParser p = new MIMEParser(); + + output ("============== Begin Static Parsing " + filename + " ============\n"); + + if (filename != null && p != null) + { + try + { + FileInputStream input = new FileInputStream (filename); + showObject (p.parseEntireMessage (input)); + input.close(); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + catch (MIMEException e) + { + System.out.println (e.getMessage()); + } + } + + output ("\n=========== Parsing Complete " + filename + " =============\n\n"); + + } + else // perform Dynamic Parsing + { + try + { + myDataSink dataSink = new myDataSink(); + //MIMEDynamicParser p = new MIMEDynamicParser (dataSink, true, true); + MIMEDynamicParser p = new MIMEDynamicParser (dataSink); + FileInputStream input = new FileInputStream (filename); + byte[] buffer = new byte[256]; + int bytesRead; + + output ("============== Begin Dynamic Parsing " + filename + " ============\n"); + + p.beginParse(); + + p.parse (input); + + //bytesRead = input.read (buffer); + //while (bytesRead > 0) + //{ + // p.parse (buffer); + // bytesRead = input.read (buffer); + //} + + p.endParse(); + input.close(); + + //showObject (dataSink.m_mimeMessage); + + output ("\n=========== Parsing Complete " + filename + " =============\n\n"); + } + catch (MIMEException e) + { + System.out.println (e.getMessage()); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + + } // bDynamic + + } // parseFileMessage + + + void output (String s) + { + try + { + logfile.write (s.getBytes()); + System.out.println (s); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + + void output (byte b[], int len) + { + try + { + logfile.write (b, 0, len); + System.out.println (new String (b, 0, len)); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + /************ MIAN ******************/ + static public void main(String args[]) + { + boolean bDynamic=true; // Dynamic Parsing by Default + String filename; + + if (args.length < 1) + { + System.out.println("usage: java testAppStr [D]"); + System.out.println("example: java testAppStr mime1.txt"); + System.exit(0); + } + + filename = args[0]; + + if (args.length > 1 && !args[1].equalsIgnoreCase("D")) + { + bDynamic = false; // Switch to Static Parsing + } + + log.init(); + log.timestampOff(); + testAppStr ta = new testAppStr(); + + // parse the message in the file + ta.parseFileMessage (filename, bDynamic); + } +} diff --git a/msgsdk/java/protocol/MIME/testapp/testAppStrLoop.java b/msgsdk/java/protocol/MIME/testapp/testAppStrLoop.java new file mode 100644 index 00000000000..0f5a8d765cc --- /dev/null +++ b/msgsdk/java/protocol/MIME/testapp/testAppStrLoop.java @@ -0,0 +1,386 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* +* Copyright (c) 1997 and 1998 Netscape Communications Corporation +* (http://home.netscape.com/misc/trademarks.html) +*/ + +/* + * This example programs demonstrates the use of Static and + * Dynamic MIME Parser. + */ + +import java.awt.*; +import java.sql.*; +import java.util.Vector; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.IOException; +import netscape.messaging.mime.*; + + +public class testAppStrLoop +{ + byte b[]; + FileOutputStream logfile; + int m_nFileNo; + public myDataSink dataSink = null; + public MIMEDynamicParser dp = null; + + public testAppStrLoop() + { + b = new byte[100000]; + m_nFileNo=0; + dataSink = null; + dp = null; + + try + { + logfile = new FileOutputStream ("testapp.log"); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + void showObject (Object o) + { + InputStream in = null; + + if (o == null) + return; + + if (o instanceof MIMEMessage) + { + output ("--------------- Start Mime Message ---------------\n"); + + MIMEMessage m = (MIMEMessage) o; + + try + { + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("message header = [" + h[i].getLine()); + } + + output ("* message body type = " + m.getBodyType() + "\n"); + output ("* message content type = " + m.getContentType() + "\n"); + output ("* message content subtype = " + m.getContentSubType() + "\n"); + output ("* message content type param = " + m.getContentTypeParams() + "\n"); + + showObject (m.getBody(false)); + output ("---------------- End Mime Message -----------------\n"); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + } + else if (o instanceof MIMEBasicPart) + { + output ("------------------- Start Mime BasicPart -------------------\n"); + + try + { + MIMEBasicPart m = (MIMEBasicPart) o; + in = m.getBodyData(); + + if (in != null) + { + int len = 0; + + if (m.getMessageDataLen() < 100000) + len = in.read (b); + + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("basic header = [" + h[i].getLine() ); + } + + output ("* basicpart content type = " + m.getContentType() + "\n"); + output ("* basicpart content subtype = " + m.getContentSubType() + "\n"); + output ("* basicpart content type param = " + m.getContentTypeParams() + "\n"); + output ("* basicpart content ID = " + m.getContentID() + "\n"); + output ("* basicpart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* basicpart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* basicpart content Description = " + m.getContentDescription() + "\n"); + output ("* basicpart content MD5 = " + m.getContentMD5() + "\n"); + output ("* basicpart getbodysize() = " + m.getSize() + "\n"); + output ("* basicpart content encoding = " + m.getContentEncoding() + "\n"); + + output (">>>>>>>>>>>>>>>>>>>> start data >>>>>>>>>>>>>>>>>>>\n"); + + // base64 + if (m.getContentEncoding() == 0) + output ("[BASE64 BINARY DATA]"); + // QP + else if (m.getContentEncoding() == 1) + output ("[QP DATA]"); + // text + else + output (b, len); + + output ("\n>>>>>>>>>>>>>>>>>>>> end data >>>>>>>>>>>>>>>>>>>>\n"); + + // base64 + if (m.getContentEncoding() == 1 && m.getMessageDataLen() < 100000) + { + FileOutputStream out = new FileOutputStream ("bodydata" + m_nFileNo++ + ".out"); + out.write (b, 0, m.getMessageDataLen()); + out.close(); + } + + in.close(); + } + } + catch (MIMEException e) + { + output (e.getMessage()); + } + catch (IOException e) + { + output (e.getMessage()); + } + + output ("------------------- End Mime BasicPart -------------------\n"); + } + else if (o instanceof MIMEMultiPart) + { + output ("------------------- Start Mime MultiPart -------------------\n"); + + MIMEMultiPart m = (MIMEMultiPart) o; + int count = m.getBodyPartCount(); + + // debug + output ("* multipart content type = " + m.getContentType() + "\n"); + output ("* multipart content subtype = " + m.getContentSubType() + "\n"); + output ("* multipart content type param = " + m.getContentTypeParams() + "\n"); + output ("* multipart content ID = " + m.getContentID() + "\n"); + output ("* multipart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* multipart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* multipart content Description = " + m.getContentDescription() + "\n"); + + if (count > 0) + for (int i = 0; i < count; i++) + { + try + { + Object part = m.getBodyPart(i, false); + showObject (part); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + } + + output ("------------------- End Mime MultiPart ------------------\n"); + } + + else if (o instanceof MIMEMessagePart) + { + output ("------------------- Start Mime MessagePart ----------------\n"); + + MIMEMessagePart m = (MIMEMessagePart) o; + + try + { + MIMEMessage part = m.getMessage(false); + + Header[] h = m.getAllHeaders(); + + if (h != null) + for (int i=0; h != null && i < h.length; i++) + { + output ("messagepart header = [" + h[i].getLine() ); + } + + output ("* messagepart content type = " + m.getContentType() + "\n"); + output ("* messagepart content subtype = " + m.getContentSubType() + "\n"); + output ("* messagepart content type param = " + m.getContentTypeParams() + "\n"); + output ("* messagepart content ID = " + m.getContentID() + "\n"); + output ("* messagepart content Disposition = " + m.getContentDisposition() + "\n"); + output ("* messagepart content Disposition params = " + m.getContentDispParams() + "\n"); + output ("* messagepart content Description = " + m.getContentDescription() + "\n"); + output ("* messagepart content encoding = " + m.getContentEncoding() + "\n"); + + showObject (part); + } + catch (MIMEException e) + { + output (e.getMessage()); + } + + output ("------------------- End Mime MessagePart -----------------\n"); + } + } + + + void parseFileMessage (String filename, boolean bDynamic) + { + if (!bDynamic) // perform Static Parsing + { + MIMEParser p = new MIMEParser(); + + output ("============== Begin Static Parsing " + filename + " ============\n"); + + if (filename != null && p != null) + { + try + { + FileInputStream input = new FileInputStream (filename); + showObject (p.parseEntireMessage (input)); + input.close(); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + catch (MIMEException e) + { + System.out.println (e.getMessage()); + } + } + + output ("\n=========== Parsing Complete " + filename + " =============\n\n"); + + } + else // perform Dynamic Parsing + { + try + { + if (dataSink == null) + dataSink = new myDataSink(); + if (dp == null) + dp = new MIMEDynamicParser (dataSink); + FileInputStream input = new FileInputStream (filename); + byte[] buffer = new byte[256]; + int bytesRead; + + output ("============== Begin Dynamic Parsing " + filename + " ============\n"); + + dp.beginParse(); + + dp.parse (input); + + //bytesRead = input.read (buffer); + //while (bytesRead > 0) + //{ + // p.parse (buffer); + // bytesRead = input.read (buffer); + //} + + dp.endParse(); + input.close(); + + //showObject (dataSink.m_mimeMessage); + + output ("\n=========== Parsing Complete " + filename + " =============\n\n"); + } + catch (MIMEException e) + { + System.out.println (e.getMessage()); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + + } // bDynamic + + } // parseFileMessage + + + void output (String s) + { + try + { + logfile.write (s.getBytes()); + System.out.println (s); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + + void output (byte b[], int len) + { + try + { + logfile.write (b, 0, len); + System.out.println (new String (b, 0, len)); + } + catch (IOException e) + { + System.out.println (e.getMessage()); + } + } + + /************ MIAN ******************/ + static public void main(String args[]) + { + boolean bDynamic=true; // Dynamic Parsing by Default + String filename=null; + String filename2=null; + + if (args.length < 1) + { + System.out.println("usage: java testAppStrLoop [D]"); + System.out.println("example: java testAppStrLoop mime1.txt"); + System.exit(0); + } + else + System.out.println("args count = " + args.length); + + filename = args[0]; + + if (args.length > 2) + { + filename2 = args[1]; + } + + if (args.length >= 2 && !args[args.length-1].equalsIgnoreCase("D")) + { + bDynamic = false; // Switch to Static Parsing + } + + log.init(); + log.timestampOff(); + testAppStrLoop ta = new testAppStrLoop(); + + // parse the message in the file + ta.parseFileMessage (filename, bDynamic); + + if (args.length > 2) + ta.parseFileMessage (filename2, bDynamic); + } +} diff --git a/msgsdk/java/protocol/Messaging/src/Common.java b/msgsdk/java/protocol/Messaging/src/Common.java new file mode 100644 index 00000000000..e4a8837a285 --- /dev/null +++ b/msgsdk/java/protocol/Messaging/src/Common.java @@ -0,0 +1,117 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +package netscape.messaging; + +/** +*Helper class for doing C-runtime equivalent operations. +*@author derekt@netscape.com +*@version 1.0 +*/ + +public class Common +{ + /** + *Converts a byte array of specified length into an int. + */ + public final int atoi( byte[] in_string ) + { + int i; + int n; + int sign; + + /////////////////////////////////////////////////////////////////////// + // Move past any leading spaces. + /////////////////////////////////////////////////////////////////////// + + for ( i = 0; Character.isSpaceChar((char)in_string[i]) && + i < in_string.length; i++ ) + { + } + + sign = (in_string[i] == '-') ? -1 : 1; + + /////////////////////////////////////////////////////////////////////// + // Store the sign of the number. + /////////////////////////////////////////////////////////////////////// + + if ( in_string[i] == '+' || in_string[i] == '-' ) + { + i++; + } + + for ( n = 0; Character.isDigit((char)in_string[i]) && + i < in_string.length; i++ ) + { + n = 10 * n + ( in_string[i] - '0'); + } + + return sign * n; + } + + /** + *Converts an integer into its ASCII representation. + */ + public final int itoa( int in_number, byte[] io_string ) + { + int i; + int sign; + + if ((sign = in_number) < 0) + { + in_number = -in_number; + } + + i = 0; + + do + { + io_string[i++] = (byte)(in_number % 10 + '0'); + } + while ((in_number /= 10) > 0); + + if ( sign < 0 ) + { + io_string[i++] = '-'; + } + reverse(io_string, i ); + return i; + } + + /** + *Helper used by itoa. + */ + protected final void reverse( byte[] io_string, int in_length ) + { + int c, i, j; + + for ( i = 0, j = in_length - 1; i < j; i++, j-- ) + { + c = io_string[i]; + io_string[i] = io_string[j]; + io_string[j] = (byte)c; + } + } +} diff --git a/msgsdk/java/protocol/Messaging/src/IO.java b/msgsdk/java/protocol/Messaging/src/IO.java new file mode 100644 index 00000000000..ff09980a235 --- /dev/null +++ b/msgsdk/java/protocol/Messaging/src/IO.java @@ -0,0 +1,633 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +package netscape.messaging; + +import java.net.Socket; +import java.io.OutputStream; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.Vector; + +/** +*Class for conducting IO with a server. It communicates directly
+*with the input and output streams associated with the socket connected to
+*the ESMTP server. It performs the handling of synchronous versus
+*asynchronous methods. It also handles PIPELINING (batching) of commands if
+*the user chose this at connection time and the ESMTP server also supports
+*PIPELINING. This class also initializes and manages the authentication and
+*security objects.
+*@author derekt@netscape.com +*@version 1.0 +*/ + +public class IO +{ + + /////////////////////////////////////////////////////////////////////////// + // Data members used for network communication + /////////////////////////////////////////////////////////////////////////// + + private Socket m_socket; + private BufferedInputStream m_inStream; + private BufferedOutputStream m_outStream; + + private int m_timeout; + private final static byte[] m_newline = { '\r', '\n' }; + + /////////////////////////////////////////////////////////////////////////// + // Error messages. + /////////////////////////////////////////////////////////////////////////// + + protected final static String errNotConnected = + new String("Must connect to server"); + protected final static String errAlreadyConnected = + new String("Already connected to server"); + +/** +*Constructor for the IO object. Initializes all data members
+*/ + public IO() + { + /////////////////////////////////////////////////////////////////////// + // Initialize data members. + /////////////////////////////////////////////////////////////////////// + + m_socket = null; + m_inStream = null; + m_outStream = null; + m_timeout = 0; + } + + public void connect( String in_server, int in_portNumber ) throws IOException + { + connect( in_server, in_portNumber, 1024, 1024 ); + } + +/** +*Open a communication channel with the server. An input stream
+*is initialized for receiving data from the server and an output stream is
+*initialized for send data to the server.
+*@param in_szServer Specifies the ESMTP server to connect to. +*@param in_portNumber Specifies the port used to communicate with. +*@exception IOException thrown if IOError. +*/ + public void connect( String in_server, + int in_portNumber, + int in_inStreamBuf, + int in_outStreamBuf ) throws IOException + { + if ( m_socket != null ) + { + throw new IOException( errAlreadyConnected ); + } + + /////////////////////////////////////////////////////////////////////// + // Initialize counters and flags. + /////////////////////////////////////////////////////////////////////// + + try + { + /////////////////////////////////////////////////////////////////// + // Initializes the socket, input buffer and output buffer. + /////////////////////////////////////////////////////////////////// + + m_socket = new Socket( in_server, in_portNumber ); + m_inStream = new BufferedInputStream( m_socket.getInputStream(), in_inStreamBuf ); + m_outStream = new BufferedOutputStream( m_socket.getOutputStream(), in_outStreamBuf ); + setTimeout( m_timeout ); + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + + public void disconnect() throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Initialize counters and flags. + /////////////////////////////////////////////////////////////////////// + + m_inStream = null; + m_outStream = null; + + if ( m_socket != null ) + { + m_socket.close(); + m_socket = null; + } + } + +/** +*Determines if a connection is currently open. +*/ + private boolean isConnected() + { + if ( m_inStream == null || m_outStream == null ) + { + return false; + } + + return true; + } + +/** +*Fill a byte array buffer. +*/ + public int read( byte[] out_buffer, int in_offset, int in_length ) throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + if ( in_length > out_buffer.length ) + { + in_length = out_buffer.length; + } + + try + { + /////////////////////////////////////////////////////////////////// + // Returns -1 if the specified amount of data is not available. + /////////////////////////////////////////////////////////////////// + + if ( (m_timeout == -1) && (m_inStream.available() < in_length) ) + { + return 0; + } + + /////////////////////////////////////////////////////////////////// + // Read the data from the input stream. + /////////////////////////////////////////////////////////////////// + + return m_inStream.read( out_buffer, in_offset, in_length ); + } + catch( InterruptedIOException e ) + { + throw e; + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + +/** +*Reads a line of data from the input stream. +*/ + public int readLine( byte[] out_buffer ) throws IOException + { + return readLine( out_buffer, 0, out_buffer.length ); + } + +/** +*Reads a line of data from the input stream. +*/ + public int readLine( byte[] out_buffer, int in_offset, int in_length ) throws IOException + { + int l_byteCount = 0; + + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + m_inStream.mark( in_length ); + + /////////////////////////////////////////////////////////////////// + // Read the data from the input stream. + /////////////////////////////////////////////////////////////////// + + do + { + if ( (m_timeout == -1) && (m_inStream.available() == 0) ) + { + m_inStream.reset(); + throw new InterruptedIOException(); + } + + out_buffer[in_offset] = (byte)m_inStream.read(); + in_offset++; + l_byteCount++; + } + while( out_buffer[in_offset-1] != '\n' ); + + return ( l_byteCount - 2 ); + } + catch( InterruptedIOException e ) + { + throw e; + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + + /** + * Read's in in_bytes of data into in_data + * @param in_data The storage for the data read in + * @param in_bytes The maximum number of bytes to read + * @param int The actual number of bytes read + */ + public int read(byte[] in_data, int in_bytes) throws IOException + { + int l_bytesRead = 0; + int l_totalBytes = 0; + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + while(l_totalBytes < in_bytes) + { + l_bytesRead = m_inStream.read(in_data, l_totalBytes, in_bytes - l_totalBytes); + if(l_bytesRead == -1) + { + if(l_totalBytes ==0) + { + return -1; + } + break; + } + l_totalBytes += l_bytesRead; + } + return l_totalBytes; + } + catch(IOException e) + { + disconnect(); + throw e; + } + } + + /** + * Read's a line of data and stores it in m_responseBuffer + * NOTE: This function strips off /r/n + */ + public void readLine(StringBuffer in_line) throws IOException + { + char l_nextChar = ' '; + boolean l_bLineRead = false; + in_line.setLength(0); + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + while(!l_bLineRead) + { + //Store a line of the server response + l_nextChar = (char)m_inStream.read(); + if((l_nextChar == '\r') || (l_nextChar == '\n')) + { + m_inStream.mark(2); + l_nextChar = (char)m_inStream.read(); + if(l_nextChar != '\n') + { + m_inStream.reset(); + } + l_bLineRead = true; + } + else if(l_nextChar == -1) + { + throw new IOException("The socket has been closed unexpectedly."); + } + else + { + in_line.append(l_nextChar); + } + } + } + catch(IOException e) + { + disconnect(); + throw e; + } + } + + +/** +*@exception IOException thrown if IOError. +*/ + public void write( byte in_byte ) throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + m_outStream.write( in_byte ); + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + +/** +*@exception IOException thrown if IOError. +*/ + public void write( String in_buffer ) throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + int l_length = in_buffer.length(); + + for ( int i = 0; i < l_length; i++ ) + { + m_outStream.write( (byte)in_buffer.charAt(i) ); + } + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + +/** +*@exception IOException thrown if IOError. +*/ + public void write( byte[] in_buffer ) throws IOException + { + write( in_buffer, 0, in_buffer.length ); + } + +/** +*@exception IOException thrown if IOError. +*/ + public void write( byte[] in_buffer, int in_offset, int in_length ) throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + /////////////////////////////////////////////////////////////////// + // Send the command to the server + /////////////////////////////////////////////////////////////////// + + m_outStream.write( in_buffer, in_offset, in_length ); + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + + + /** + * Sends client commands to IMAP server synchronously. + * @param in_szCommand The IMAP command to invoke on the server + * @return boolean TRUE if command executed, FALSE otherwise. Note: Does not + * indicate successful completion of the command, just that it has been invoked + * on the IMAP Server. You must look in in_command to find the server response + * once the sendCommand() call returns. + */ + public void write(Vector in_data) throws IOException + { + String l_dataString = null; + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + for(int i = 0; i < in_data.size(); i++) + { + l_dataString = (String)in_data.elementAt(i); + if(l_dataString != null) + { + for(int j = 0; j < l_dataString.length(); j++) + { + m_outStream.write((byte)l_dataString.charAt(j)); + } + } + } + } + catch(IOException e) + { + disconnect(); + throw e; + } + } + + +/** +*@exception IOException thrown if IOError. +*/ + public void send( String in_buffer, boolean in_bufferData ) throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + int l_length = in_buffer.length(); + + for ( int i = 0; i < l_length; i++ ) + { + m_outStream.write( (byte)in_buffer.charAt(i) ); + } + + m_outStream.write( m_newline ); + + if ( in_bufferData == false ) + { + m_outStream.flush(); + } + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + +/** +*@exception IOException thrown if IOError. +*/ + public void send( byte[] in_buffer, boolean in_bufferData ) throws IOException + { + send( in_buffer, 0, in_buffer.length, in_bufferData ); + } + +/** +*@exception IOException thrown if IOError. +*/ + public void send( byte[] in_buffer, + int in_offset, + int in_length, + boolean in_bufferData ) throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + m_outStream.write( in_buffer, in_offset, in_length ); + m_outStream.write( m_newline ); + + if ( in_bufferData == false ) + { + m_outStream.flush(); + } + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + +/** +*/ + public void flush() throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } + + try + { + /////////////////////////////////////////////////////////////////// + // Flush the output stream. + /////////////////////////////////////////////////////////////////// + + m_outStream.flush(); + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + +/** +*Sets the socket timeout for reading data from the socket input buffer. +*/ + public void setTimeout( int in_timeout ) throws IOException + { + /////////////////////////////////////////////////////////////////// + // Make sure there is a valid connection. + /////////////////////////////////////////////////////////////////// + + // We should be able to set timeouts while disconnected. +/* + if ( isConnected() == false ) + { + throw new IOException(errNotConnected); + } +*/ + if ( in_timeout < -1 ) + { + throw new IllegalArgumentException(); + } + + m_timeout = in_timeout; + + if ( isConnected() == true ) + { + try + { + if ( m_timeout != -1 ) + { + m_socket.setSoTimeout( m_timeout ); + } + } + catch ( IOException e ) + { + disconnect(); + throw e; + } + } + } +} diff --git a/msgsdk/java/protocol/Messaging/src/Makefile b/msgsdk/java/protocol/Messaging/src/Makefile new file mode 100644 index 00000000000..66d14f9d095 --- /dev/null +++ b/msgsdk/java/protocol/Messaging/src/Makefile @@ -0,0 +1,54 @@ +# environment +#SHELL = /usr/bin/ksh + +# commands +JAVAC = javac +ARCH = $(shell uname -s) + +ifeq ($(ARCH), SunOS) + ARCH = SOLARIS +endif + +ifeq ($(ARCH), HP-UX) + ARCH = HPUX +endif + +# java flags +DEBUGJAVAFLAG = +OPTJAVAFLAG = -d $(CLASSDIR) +JAVAFLAGS = $(OTHERJAVAFLAGS) $(OPTJAVAFLAG) $(DEBUGJAVAFLAG) + +# files and directories +CLASSDIR = ../../../built/$(ARCH)/protocol + +#CLASSPATH = .:$(CLASSDIR):$(JDKCLASSPATH) + +SRCS = \ + IO.java \ + Common.java + +OBJS = ${SRCS:.java=.class} + +TARGET = package + +.SUFFIXES: .java .class + +all: $(CLASSDIR) $(TARGET) + +install: $(TARGET) + foreach f ( $(OBJS) ) \ + mv -f $$f $(CLASSDIR)/$$f \ + end + +$(TARGET): $(OBJS) +$(CLASSDIR): + echo mkdir $(CLASSDIR) + - mkdir -p $(CLASSDIR) + +.java.class: $(SRCS) + $(JAVAC) $(JAVAFLAGS) $< + +clean: + echo $(ARCH) + rm -f ../../../built/$(ARCH)/netscape/messaging/*.class + diff --git a/msgsdk/java/protocol/SMTP/src/ISMTPSink.java b/msgsdk/java/protocol/SMTP/src/ISMTPSink.java new file mode 100644 index 00000000000..66e7b12c2df --- /dev/null +++ b/msgsdk/java/protocol/SMTP/src/ISMTPSink.java @@ -0,0 +1,270 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +package netscape.messaging.smtp; + +/** +*ISMTPSink is the interface for the response sink for all SMTP commands. +*

The ISMTPSink interface contains callback methods for each client call. +*The client’s processResponses call invokes the appropriate +*interface callback method. +*

To utilize the SMTP client object, you must extend this +*interface. As a convenience, the Messaging Access SDK provides the +*SMTPSink class, which implements the ISMTPSink interface. +*You can save a step by extending the SMTPSink class, or +*you can implement your own class based on the ISMTPSink +*interface. The constructor for the SMTPClient class takes an +*ISMTPSink interface as a parameter. +*

These methods return standard SMTP Response Codes, as defined in RFC 821. +*See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP" +*for more information. For detailed information about SMTP, +*see RFC 821. (For the URL, see "Where to Find More Information" in "About This Book.") +*@author derekt@netscape.com +*@version 1.0 +*/ + +public interface ISMTPSink +{ + /** + *Notification for the response to the BDAT command. + *Sends binary data chunks of the specified size to the server. + *When using the sendCommand method, send data with the data method + *and not with bdat. + *Note: bdat is not supported by Messaging Server 4.0. Use data instead. + *

See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#bdat + * @see #data + * @see #send + */ + public void bdat( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the connection to the server. + *

Connects to the server using the default port (25). + *See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#connect + * @see #quit + */ + public void connect( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the DATA command. + *Prepares to send data to the server. The sendCommand method requires + *sending data with the data method and not with bdat. + *For more information, see RFC 821 (URL: go to SMTP RFCs). + *See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#data + * @see #bdat + * @see #send + */ + public void data( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the EHLO command. + *Along with ehloComplete, returns extended server information. + *Can get extended server information (which can be multiline). + *See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_serverInfo Extension supported by the server. + * @see SMTPClient#ehlo + * @see #ehloComplete + */ + public void ehlo( int in_responseCode, StringBuffer in_serverInfo ); + + /** + *Notification for the completion of the EHLO command. + *Along with ehlo, returns extended server information. + * @see SMTPClient#ehlo + * @see #ehlo + */ + public void ehloComplete(); + + /** + *Error notification. + *Called when an error occurs. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_errorMessage Text that describes the error. + * @exception SMTPServerException If a server response error occurs. + * @see SMTPClient#processResponses + */ + public void error( int in_responseCode, StringBuffer in_errorMessage ) throws SMTPServerException; + + /** + *Notification for the response to the EXPN command. + *Along with expandComplete, gets the email address of the specified user. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_user User for whom to get an email address. + * @see SMTPClient#expand + * @see #expandComplete + */ + public void expand( int in_responseCode, StringBuffer in_user ); + + /** + *Notification for the completion of the EXPN command. + *Along with expand, gets the email address of the specified user. + * @see SMTPClient#expand + * @see #expand + */ + public void expandComplete(); + + /** + *Notification for the response to the HELP command. + *Along with helpComplete, gets help on the specified topic, + *which can be multiline. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_help Line of help text. + * @see SMTPClient#help + * @see #helpComplete + */ + public void help( int in_responseCode, StringBuffer in_help ); + + /** + *Notification for the completion of the HELP command. + *Along with help, gets help on a specified topic. + * @see SMTPClient#help + * @see #help + */ + public void helpComplete(); + + /** + *Notification for the response to the MAIL FROM command. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#mailFrom + * @see #rcptTo + */ + public void mailFrom( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the NOOP command. + *

Server responds to commands with a "still here" response. + *Sending the noop method does nothing except force this response. + *Can be used to maintain server connection, perhaps being issued + *at timed intervals to make sure that the server is still active. + *

Resets the autologout timer inside the server. + *Not needed by applications that do not need to maintain the connection. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#noop + */ + public void noop( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the QUIT command. + *Ends the session. If the session is in the Authentication state, + *the server closes the server connection. If the session is in + *the Transaction state, the server goes into the Update state + *and deletes any marked messages, and then quits. + * See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#quit + */ + public void quit( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the RCPT TO command. + *Gets the address of the recipient of the message. Called once for each + *recipient; should follow the SMTP_mailFrom function. + * See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#rcptTo + * @see #mailFrom + */ + public void rcptTo( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the RSET command. + *Cancels the current mail transfer and all current processes, + *discards data, and clears all states. Returns to the state that + *followed the last method that sent the EHLO command. + *Returns a response code and optional response text. + * See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#reset + * @see #ehlo + */ + public void reset( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to data sent to the server. + *Returns a response code and optional response text. + *This method requires using the SMTP_data command to send data, + *rather than SMTP_bdat. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#send + * @see #data + */ + public void send( int in_responseCode, StringBuffer in_responseMessage ); + + /** + *Notification for the response to the extended method. + *Along with sendCommandComplete, extends the protocol to + *meet client application needs. Sends commands that are not supported + *by the Messaging SDK implementation of SMTP. + *Can get extended server information, possibly multiline. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseLine Buffer for the response message. + * @see SMTPClient#sendCommand + * @see #sendCommandComplete + */ + public void sendCommand( int in_responseCode, StringBuffer in_responseLine ); + + /** + *Notification for the completion of the extended command. + *Along with sendCommand, extends the protocol to meet client application + *needs. + * @see SMTPClient#sendCommand + * @see #sendCommand + */ + public void sendCommandComplete(); + + /** + *Notification for the response to the VRFY command. + *Returns a response code and optional response text. + *For more information, see RFC 821 (URL are listed in SMTP RFCs). + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Buffer for the response message. + * @see SMTPClient#verify + */ + public void verify( int in_responseCode, StringBuffer in_responseMessage ); +} diff --git a/msgsdk/java/protocol/SMTP/src/Makefile b/msgsdk/java/protocol/SMTP/src/Makefile new file mode 100644 index 00000000000..f66ac62fd7a --- /dev/null +++ b/msgsdk/java/protocol/SMTP/src/Makefile @@ -0,0 +1,57 @@ +# environment +#SHELL = /usr/bin/ksh + +# commands +JAVAC = javac +ARCH = $(shell uname -s) + +ifeq ($(ARCH), SunOS) + ARCH = SOLARIS +endif + +ifeq ($(ARCH), HP-UX) + ARCH = HPUX +endif + +# java flags +DEBUGJAVAFLAG = +OPTJAVAFLAG = -d $(CLASSDIR) +JAVAFLAGS = $(OTHERJAVAFLAGS) $(OPTJAVAFLAG) $(DEBUGJAVAFLAG) + +# files and directories +CLASSDIR = ../../../built/$(ARCH)/protocol + +#CLASSPATH = .:$(CLASSDIR):$(JDKCLASSPATH) + +SRCS = \ + SMTPException.java \ + SMTPServerException.java \ + ISMTPSink.java \ + SMTPSink.java \ + SMTPClient.java + +OBJS = ${SRCS:.java=.class} + +TARGET = package + +.SUFFIXES: .java .class + +all: $(CLASSDIR) $(TARGET) + +install: $(TARGET) + foreach f ( $(OBJS) ) \ + mv -f $$f $(CLASSDIR)/$$f \ + end + +$(TARGET): $(OBJS) +$(CLASSDIR): + echo mkdir $(CLASSDIR) + - mkdir -p $(CLASSDIR) + +.java.class: $(SRCS) + $(JAVAC) $(JAVAFLAGS) $< + +clean: + echo $(ARCH) + rm -f ../../../built/$(ARCH)/netscape/messaging/smtp/*.class + diff --git a/msgsdk/java/protocol/SMTP/src/SMTPClient.java b/msgsdk/java/protocol/SMTP/src/SMTPClient.java new file mode 100644 index 00000000000..7b116c20d13 --- /dev/null +++ b/msgsdk/java/protocol/SMTP/src/SMTPClient.java @@ -0,0 +1,1418 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +package netscape.messaging.smtp; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import netscape.messaging.*; + +/** +*The SMTPClient class represents the SMTP client. +*The client uses this class for communicating with a +*server using the SMTP protocol. +*The SMTP client conforms to the specifications of the client +*described in RFC 821. +*

For a list of the SMTP RFCs referenced in the +*Messaging Access SDK and their URLs, +*see "Where to Find More Information" in "About This Book." +*@author derekt@netscape.com +*@version 1.0 +*/ + +public class SMTPClient +{ + /////////////////////////////////////////////////////////////////////////// + // Internal class used for socket I/O. + /////////////////////////////////////////////////////////////////////////// + private IO m_io; + + /////////////////////////////////////////////////////////////////////////// + // Data member used for C-runtime equivalent routines. + /////////////////////////////////////////////////////////////////////////// + private Common m_common; + + /////////////////////////////////////////////////////////////////////////// + // Data member used to store the timeout value. + /////////////////////////////////////////////////////////////////////////// + private int m_timeout; + + /////////////////////////////////////////////////////////////////////////// + // Last character sent to the server used for byte stuffing messages. + /////////////////////////////////////////////////////////////////////////// + private byte m_lastSentChar; + + /////////////////////////////////////////////////////////////////////////// + // State variable indicating if the DATA command was last executed. + /////////////////////////////////////////////////////////////////////////// + private boolean m_fSendingData; + + /////////////////////////////////////////////////////////////////////////// + // Boolean variable to determine if we can send another command. + /////////////////////////////////////////////////////////////////////////// + private boolean m_mustProcess; + + /////////////////////////////////////////////////////////////////////////// + // Callback data members. + /////////////////////////////////////////////////////////////////////////// + + private Vector m_pendingList; + private ISMTPSink m_notifySink; + + /////////////////////////////////////////////////////////////////////////// + // Data members specific for PIPELINING mode. + /////////////////////////////////////////////////////////////////////////// + + private Vector m_pipelinedCommandList; + private boolean m_fPipeliningSupported; + private boolean m_fPipeliningEnabled; + + /////////////////////////////////////////////////////////////////////////// + // Reused response objects. + /////////////////////////////////////////////////////////////////////////// + + private StringBuffer m_responseMessage; + + /////////////////////////////////////////////////////////////////////////// + // Byte arrays that are reused for commands and responses. + /////////////////////////////////////////////////////////////////////////// + + private byte[] m_response; + private byte[] m_messageData; + private byte[] m_itoaBuffer; + + /////////////////////////////////////////////////////////////////////////// + // Constants. + /////////////////////////////////////////////////////////////////////////// + + private final static int m_defaultPort = 25; + private final static int m_minChunkSize = 1024; + private final static int m_maxReplyLine = 512; + + private final static byte[] m_bdat = { 'B', 'D', 'A', 'T', ' ' }; + private final static byte[] m_data = { 'D', 'A', 'T', 'A' }; + private final static byte[] m_ehlo = { 'E', 'H', 'L', 'O', ' ' }; + private final static byte[] m_expn = { 'E', 'X', 'P', 'N', ' ' }; + private final static byte[] m_help = { 'H', 'E', 'L', 'P', ' ' }; + private final static byte[] m_mail = { 'M', 'A', 'I', 'L', ' ', 'F', 'R', 'O', 'M', ':', '<' }; + private final static byte[] m_noop = { 'N', 'O', 'O', 'P' }; + private final static byte[] m_quit = { 'Q', 'U', 'I', 'T' }; + private final static byte[] m_rcpt = { 'R', 'C', 'P', 'T', ' ', 'T', 'O', ':', '<' }; + private final static byte[] m_rset = { 'R', 'S', 'E', 'T' }; + private final static byte[] m_vrfy = { 'V', 'R', 'F', 'Y', ' ' }; + //private final static byte[] m_rBracket = { '>', ' ' }; + private final static byte[] m_rBracket = { '>' }; + private final static byte[] m_newline = { '\r', '\n' }; + private final static byte[] m_eomessage = { '\r', '\n', '.' }; + private final static byte[] m_bdatLast = { ' ', 'L', 'A', 'S', 'T' }; + private final static byte[] m_empty = {}; + + /////////////////////////////////////////////////////////////////////////// + // Command types for SMTP. + /////////////////////////////////////////////////////////////////////////// + + private final static int BDAT = 0; + private final static int CONN = 1; + private final static int DATA = 2; + private final static int EHLO = 3; + private final static int EXPN = 4; + private final static int HELP = 5; + private final static int MAIL = 6; + private final static int NOOP = 7; + private final static int QUIT = 8; + private final static int RCPT = 9; + private final static int RSET = 10; + private final static int SEND = 11; + private final static int SENDCOMMAND = 12; + private final static int VRFY = 13; + + private final static Integer BDAT_OBJ = new Integer(BDAT); + private final static Integer CONN_OBJ = new Integer(CONN); + private final static Integer DATA_OBJ = new Integer(DATA); + private final static Integer EHLO_OBJ = new Integer(EHLO); + private final static Integer EXPN_OBJ = new Integer(EXPN); + private final static Integer HELP_OBJ = new Integer(HELP); + private final static Integer MAIL_OBJ = new Integer(MAIL); + private final static Integer NOOP_OBJ = new Integer(NOOP); + private final static Integer QUIT_OBJ = new Integer(QUIT); + private final static Integer RCPT_OBJ = new Integer(RCPT); + private final static Integer RSET_OBJ = new Integer(RSET); + private final static Integer SEND_OBJ = new Integer(SEND); + private final static Integer SENDCOMMAND_OBJ = new Integer(SENDCOMMAND); + private final static Integer VRFY_OBJ = new Integer(VRFY); + + /////////////////////////////////////////////////////////////////////////// + // Error messages for SMTP. + /////////////////////////////////////////////////////////////////////////// + + private final static String errProcessResponses = new String("Must call processResponses()"); + private final static String errSendingData = new String("Must send data immediately after the Data() command"); + private final static String errNoPipelining = new String("Pipelining is not supported"); + private final static String errParse = new String("Error while parsing response"); + + /** + *Constructor for SMTPClient that takes an SMTPSink as a parameter. + *@param in_sink Response sink that implements the ISMTPSink interface. + *@see ISMTPSink + */ + public SMTPClient( ISMTPSink in_sink ) + { + /////////////////////////////////////////////////////////////////////// + // Variables that get initialized once in the constructor. + /////////////////////////////////////////////////////////////////////// + + m_io = new IO(); + m_common = new Common(); + m_timeout = 0; + m_pendingList = new Vector(); + m_notifySink = in_sink; + + m_response = new byte[m_maxReplyLine]; + m_messageData = new byte[m_minChunkSize]; + m_itoaBuffer = new byte[m_maxReplyLine]; + m_responseMessage = new StringBuffer( m_maxReplyLine ); + + /////////////////////////////////////////////////////////////////////// + // Variables that get initialized in the constructor and on every new + // connection. + /////////////////////////////////////////////////////////////////////// + + m_fSendingData = false; + m_fPipeliningSupported = false; + m_fPipeliningEnabled = false; + m_lastSentChar = '\n'; + } + + /////////////////////////////////////////////////////////////////////////// + // Protocol commands + /////////////////////////////////////////////////////////////////////////// + + /** + *Sends binary data chunks to the server. + *

Not to be used with the data() command. + *With SMTPClient.send(), data should be sent with SMTPClient.data + *and not with SMTPClient.bdat. + *For more information, see "Sending the Message." + *

Note: bdat is not supported by Messaging Server 4.0. Use SMTPClient.data instead. + *@param in_data Array for the raw data to send to the server. + *@param in_offset Offset for data. + *@param in_length Number of bytes to send. + *@param in_fLast Indicates whether this is the last chunk of data to send. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#bdat + *@see ISMTPSink#error + *@see #data + */ + public synchronized void bdat( byte[] in_data, + int in_offset, + int in_length, + boolean in_fLast ) throws IOException + { + int l_numBytes; + + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_length < 0 || in_offset < 0 ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.write( m_bdat ); + + l_numBytes = m_common.itoa( in_length, m_itoaBuffer ); + m_io.write( m_itoaBuffer, 0, l_numBytes ); + + if ( in_fLast ) + { + m_io.write( m_bdatLast ); + } + + m_io.write( m_newline, 0, m_newline.length ); + m_io.write( in_data, in_offset, in_length ); + m_io.write( m_newline, 0, m_newline.length ); + + m_pendingList.addElement( BDAT_OBJ ); + + if ( m_fPipeliningEnabled == false ) + { + m_io.flush(); + m_mustProcess = true; + } + + } + + /** + *Connects to the server using the default port. + *

To specify the connection port, use the + *form of connect that takes a port number. + *@param in_server Name of the host server to connect to. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#connect + *@see ISMTPSink#error + *@see #quit + *@see #connect + */ + public synchronized void connect( String in_server ) throws IOException + { + connect( in_server, m_defaultPort ); + } + + /** + *Connects to the server using the specified port. + *

To use the default connection port, use the + *form of connect that takes only the server name. + *@param in_server Name of the host server to connect to. + *@param in_port Number of the port to connect to. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#connect + *@see ISMTPSink##error + *@see #quit + *@see #connect + */ + public synchronized void connect( String in_server, int in_port ) throws IOException + { + if ( in_server == null || in_port < 0 ) + { + throw new IllegalArgumentException(); + } + + m_fSendingData = false; + m_fPipeliningSupported = false; + m_fPipeliningEnabled = false; + m_lastSentChar = '\n'; + + m_pendingList.removeAllElements(); + + m_io.connect( in_server, in_port, m_minChunkSize, m_messageData.length ); + + m_io.setTimeout( m_timeout ); + + m_pendingList.addElement( CONN_OBJ ); + + m_mustProcess = true; + } + + /** + *Prepares to send data to the server. + *With SMTPClient.send(), + *data should be sent with SMTPClient.data and not with SMTPClient.bdat. + *For more information, see "Sending the Message." + *Note: Not to be used with the bdat() command. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#data + *@see ISMTPSink#error + */ + public synchronized void data() throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.send( m_data, false ); + + m_pendingList.addElement( DATA_OBJ ); + + m_mustProcess = true; + } + + /** + *Closes the socket connection with the server. + *Can be used to perform a high level "Cancel" while + *sending a message. + *NOTE: Do not call processResponses() after this method. + *@exception IOException thrown on IO error. + */ + public synchronized void disconnect() throws IOException + { + m_io.disconnect(); + } + + /** + *Determines the ESMTP server extensions. + *The callback on the response + *sink identifies the various SMTP extensions supported by the server. + *@param in_domain The domain name. + *@exception IOException thrown on IO error. + *@see ISMTPSink#ehlo + *@see ISMTPSink#ehloComplete + *@see ISMTPSink#error + */ + public synchronized void ehlo( String in_domain ) throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_domain == null ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.write( m_ehlo ); + m_io.send( in_domain , false ); + + m_pendingList.addElement( EHLO_OBJ ); + m_mustProcess = true; + } + + /** + *Expands a given mailing list. + *Gets the email addresses of the users on the mailing list. + *@param in_mailingList The mailing list to expand. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#expand + *@see ISMTPSink#expandComplete + *@see ISMTPSink#error + */ + public synchronized void expand( String in_mailingList ) throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_mailingList == null ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.write( m_expn ); + m_io.send( in_mailingList, false ); + + m_pendingList.addElement( EXPN_OBJ ); + m_mustProcess = true; + } + + /** + *Obtains help on a given topic. + *@param in_helpTopic One-word help topic to get information on. If null, + *user may get Help on Help or a default. See implementation on the server. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#help + *@see ISMTPSink#helpComplete + *@see ISMTPSink#error + */ + public synchronized void help( String in_helpTopic ) throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.write( m_help ); + + if ( in_helpTopic != null ) + { + m_io.send( in_helpTopic, false ); + } + else + { + m_io.send( m_empty, false ); + } + + m_pendingList.addElement( HELP_OBJ ); + m_mustProcess = true; + } + + /** + *Sets the sender of the message with optional ESMTP parameters. + *@param in_reverseAddress Address of the sender of the message. + *@param in_esmtpParams Any ESMTP (Extended SMTP) parameter. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#mailFrom + *@see ISMTPSink#error + *@see #rcptTo + */ + public synchronized void mailFrom( String in_reverseAddress, String in_esmtpParams ) throws IOException + { + int count; + + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_reverseAddress == null ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.write( m_mail ); + m_io.write( in_reverseAddress ); + m_io.write( m_rBracket ); + + if ( in_esmtpParams != null ) + { + m_io.write( in_esmtpParams ); + } + + m_io.send( m_empty, m_fPipeliningEnabled ); + m_pendingList.addElement( MAIL_OBJ ); + + if ( m_fPipeliningEnabled == false ) + { + m_mustProcess = true; + } + } + + /** + *Gets positive server response; does not affect SMTP session. + *Issues Noop command. + *

The server responds to commands with a "still here" response. + *Sending the noop method does nothing except force this server response. + *Can be used to maintain server connection, perhaps being issued + *at timed intervals to make sure that the server is still active. + *Not needed by applications that do something and do not maintain a connection with the server. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#noop + *@see ISMTPSink#error + */ + public synchronized void noop() throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.send( m_noop, false ); + + m_pendingList.addElement( NOOP_OBJ ); + m_mustProcess = true; + } + + /** + *Closes the connection with the server. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#quit + *@see ISMTPSink#error + */ + public synchronized void quit() throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.send( m_quit, false ); + + m_pendingList.addElement( QUIT_OBJ ); + m_mustProcess = true; + } + + /** + *Sets the recipient of the message with optional ESMTP parameters. + *@param in_forwardAddress Address of the message recipient. + *@param in_esmtpParams Any ESMTP parameters to set. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#rcptTo + *@see ISMTPSink#error + */ + public synchronized void rcptTo( String in_forwardAddress, String in_esmtpParams ) throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_forwardAddress == null ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.write( m_rcpt ); + m_io.write( in_forwardAddress ); + m_io.write( m_rBracket ); + + if ( in_esmtpParams != null ) + { + m_io.write( in_esmtpParams ); + } + + m_io.send( m_empty, m_fPipeliningEnabled ); + m_pendingList.addElement( RCPT_OBJ ); + + if ( m_fPipeliningEnabled == false ) + { + m_mustProcess = true; + } + } + + /** + *Resets the state of the server; flushes any sender and recipient + *information. + *

Cancels the current mail transfer and all current processes, + *discards data, and clears all states. Returns to the state + *that followed the last method that sent the EHLO command. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#reset + *@see ISMTPSink#error + *@see #ehlo + */ + public synchronized void reset() throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.send( m_rset, m_fPipeliningEnabled ); + m_pendingList.addElement( RSET_OBJ ); + } + + /** + *Sends message data to the server. + *NOTE: To be used with the data() command and not with the bdat() command. + *@param in_inputStream Input stream containing the data to send. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#send + *@see ISMTPSink#error + *@see #data + */ + public synchronized void send( InputStream in_inputStream ) throws IOException + { + int l_offset; + int l_byteCount; + byte l_dot = (byte)'.'; + byte l_lineFeed = (byte)'\n'; + + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_inputStream == null ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == false ) + { + throw new SMTPException(errSendingData); + } + + /////////////////////////////////////////////////////////////////// + // Send the raw message data to the server and return the response. + /////////////////////////////////////////////////////////////////// + + do + { + /////////////////////////////////////////////////////////////// + // Read in the data from the stream. + /////////////////////////////////////////////////////////////// + + l_byteCount = in_inputStream.read( m_messageData ); + + /////////////////////////////////////////////////////////////// + // Create the string in order to search for the "\n." sequence. + // and send the data. + /////////////////////////////////////////////////////////////// + + l_offset = 0; + + for ( int count = 0; count < l_byteCount; count++ ) + { + if ( (m_lastSentChar == l_lineFeed) && + (l_dot == m_messageData[count]) ) + { + m_io.write( m_messageData, l_offset, count-l_offset ); + m_io.write( l_dot ); + l_offset = count; + } + m_lastSentChar = m_messageData[count]; + } + + if ( l_offset < l_byteCount ) + { + m_io.write( m_messageData, l_offset, l_byteCount-l_offset ); + } + } + while ( l_byteCount != -1 ); + + m_io.send( m_eomessage, m_fPipeliningEnabled ); + + m_lastSentChar = l_lineFeed; + m_pendingList.addElement( SEND_OBJ ); + + m_fSendingData = false; + + if ( m_fPipeliningEnabled == false ) + { + m_mustProcess = true; + } + } + + /** + *Sends an unsupported command to the server. + *Sends commands that are not supported by the Messaging Access SDK + *implementation of SMTP. + *

NOTE: This method is primarily intended to support extensions + *to the protocol to meet client application needs. + *@param in_command Raw command to send to the server. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#sendCommand + *@see ISMTPSink#sendCommandComplete + *@see ISMTPSink#error + */ + public synchronized void sendCommand( String in_command ) throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_command == null ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.send( in_command, false ); + + m_pendingList.addElement( SENDCOMMAND_OBJ ); + m_mustProcess = true; + } + + /** + *Verifies a username. + *@param in_user User name to verify. + *@exception IOException If an I/O error occurs. + *@see ISMTPSink#verify + *@see ISMTPSink#error + */ + public synchronized void verify( String in_user ) throws IOException + { + /////////////////////////////////////////////////////////////////////// + // Error checking before proceeding with command. + /////////////////////////////////////////////////////////////////////// + + if ( in_user == null ) + { + throw new IllegalArgumentException(); + } + + if ( m_mustProcess ) + { + throw new SMTPException(errProcessResponses); + } + + if ( m_fSendingData == true ) + { + throw new SMTPException(errSendingData); + } + + m_io.write( m_vrfy ); + m_io.send( in_user, false ); + + m_pendingList.addElement( VRFY_OBJ ); + m_mustProcess = true; + } + + /** + *Reads in responses from the server and invokes the appropriate sink methods. + *

Processes the server responses for API commands. + *It invokes the callback methods provided by the user for all + *responses that are available at the time of execution. + *NOTE: If a timeout occurs the user can continue by calling processResponses() again. + *@exception SMTPServerException If a server error occurs. + *@exception InterruptedIOException If a time-out occurs. + *@exception IOException If an I/O error occurs. + */ + public void processResponses() throws IOException + { + synchronized(m_responseMessage) + { + try + { + Integer l_commandType; + + m_io.flush(); + + /////////////////////////////////////////////////////////////////// + // Prcoess as many of the responses as possible. + /////////////////////////////////////////////////////////////////// + + while ( !m_pendingList.isEmpty() ) + { + l_commandType = (Integer)m_pendingList.firstElement(); + + switch( l_commandType.intValue() ) + { + case BDAT: + parseBdat(); + break; + case CONN: + parseConnect(); + break; + case DATA: + parseData(); + break; + case EHLO: + parseEhlo(); + break; + case EXPN: + parseExpand(); + break; + case HELP: + parseHelp(); + break; + case MAIL: + parseMail(); + break; + case NOOP: + parseNoop(); + break; + case QUIT: + parseQuit(); + m_io.disconnect(); + break; + case RCPT: + parseRcpt(); + break; + case RSET: + parseReset(); + break; + case SEND: + parseSend(); + break; + case SENDCOMMAND: + parseSendCommand(); + break; + case VRFY: + parseVerify(); + break; + } + + m_pendingList.removeElementAt( 0 ); + } + + m_mustProcess = false; + } + catch( SMTPServerException e ) + { + m_mustProcess = false; + m_pendingList.removeElementAt( 0 ); + throw e; + } + } + } + + /////////////////////////////////////////////////////////////////////////// + // Preferences + /////////////////////////////////////////////////////////////////////////// + + /** + *Sets the size of the data chunks that are read from the input stream and + *sent to the server. The minimum chunk size is 1024. + *NOTE: Do not call processResponses() after this method. + *@param in_chunkSize Size of chunk used for sending messages using the send() method. Minimum chunk size: 1024. Default: 1 K. + *@exception IOException thrown on IO error. + *@see #send + */ + public synchronized void setChunkSize( int in_chunkSize ) + { + synchronized(m_responseMessage) + { + if ( in_chunkSize < m_minChunkSize ) + { + throw new IllegalArgumentException(); + } + + m_messageData = new byte[in_chunkSize]; + } + } + + /** + *Registers a new response sink, overriding the one passed into the + *constructor or any one set afterwards. + *NOTE: Do not call processResponses() after setResponseSink(). + *@param in_responseSink The new ISMTPSink to use. + *@exception IOException If an I/O error occurs. + *@see #processResponses + */ + public synchronized void setResponseSink( ISMTPSink in_responseSink ) + { + synchronized(m_responseMessage) + { + if ( in_responseSink == null ) + { + throw new IllegalArgumentException(); + } + + m_notifySink = in_responseSink; + } + } + + /** + *Sets the amount of time allowed to wait + *before returning control to the user. + *NOTE: Do not call processResponses() after this method. + *@param in_timeout Time-out period to set. Values, in milliseconds: + *

    + *
  • 0 = infinite time-out (default) + *
  • -1 = no waiting + *
  • > 0 = length of time-out period + *
+ *@exception IOException If an I/O error occurs. + *@see #processResponses + */ + public synchronized void setTimeout( int in_timeout ) throws IOException + { + synchronized(m_responseMessage) + { + if ( in_timeout < -1 ) + { + throw new IllegalArgumentException(); + } + + m_timeout = in_timeout; + + if ( m_io != null ) + { + m_io.setTimeout( m_timeout ); + } + } + } + + /** + *Enables PIPELINING (batching) of commands, if supported by the server. + *If PIPELINING is not supported by the server, an SMTPException + *is thrown. The user can determine if PIPELINING is supported through the + *notification of the ehlo() method. + *

NOTE: Do not call processResponses() after this method. + *@param in_enablePipelining Boolean value to enable/disable PIPELINING. + *@exception SMTPException If PIPELINING is not supported by the server. + *@see #ehlo + */ + public synchronized void setPipelining( boolean in_enablePipelining ) throws SMTPException + { + synchronized(m_responseMessage) + { + if ( in_enablePipelining ) + { + if ( m_fPipeliningSupported ) + { + m_fPipeliningEnabled = true; + } + else + { + throw new SMTPException(errNoPipelining); + } + } + else + { + m_fPipeliningEnabled = false; + } + } + } + + /////////////////////////////////////////////////////////////////////////// + // Internal functions + /////////////////////////////////////////////////////////////////////////// + + /** + *Separates the response code from the message part of the response. + */ + private final int setStatusInfo( + byte[] in_response, + int in_length, + StringBuffer out_responseMessage ) throws IOException + { + int l_responseCode; + + if ( in_length < 4 || + Character.isDigit((char)in_response[0]) == false || + Character.isDigit((char)in_response[1]) == false || + Character.isDigit((char)in_response[2]) == false ) + { + throw new SMTPException(errParse); + } + + l_responseCode = ( (Character.digit( (char)in_response[0], 10 ) * 100) + + (Character.digit( (char)in_response[1], 10 ) * 10) + + (Character.digit( (char)in_response[2], 10 ) * 1) ); + + out_responseMessage.setLength( in_length - 4 ); + + for ( int count = 0; count < (in_length-4); count++ ) + { + out_responseMessage.setCharAt( count, (char)in_response[count+4] ); + } + + return l_responseCode; + } + + /////////////////////////////////////////////////////////////////////////// + // Methods for parsing the responses and invoking sink notifications. + /////////////////////////////////////////////////////////////////////////// + + private final void parseBdat() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.bdat( l_rCode, m_responseMessage ); + } + } + + private final void parseConnect() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.connect( l_rCode, m_responseMessage ); + } + } + + private final void parseData() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.data( l_rCode, m_responseMessage ); + m_fSendingData = true; + } + } + + private final void parseEhlo() throws IOException + { + int l_rCode; + int l_byteCount; + + do + { + l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, + l_byteCount, + m_responseMessage ); + + if ( m_fPipeliningSupported == false && + m_responseMessage.length() >= 10 ) + { + if ( m_responseMessage.charAt( 0 ) == 'P' && + m_responseMessage.charAt( 1 ) == 'I' && + m_responseMessage.charAt( 2 ) == 'P' && + m_responseMessage.charAt( 3 ) == 'E' && + m_responseMessage.charAt( 4 ) == 'L' && + m_responseMessage.charAt( 5 ) == 'I' && + m_responseMessage.charAt( 6 ) == 'N' && + m_responseMessage.charAt( 7 ) == 'I' && + m_responseMessage.charAt( 8 ) == 'N' && + m_responseMessage.charAt( 9 ) == 'G' ) + { + m_fPipeliningSupported = true; + } + } + + if ( l_rCode >= 400 ) + { + try + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + catch ( SMTPServerException e ) + { + while( '-' == (char)m_response[3] ) + { + m_io.readLine( m_response ); + } + throw e; + } + } + else + { + m_notifySink.ehlo( l_rCode, m_responseMessage ); + } + } + while( '-' == (char)m_response[3] ); + + m_notifySink.ehloComplete(); + } + + private final void parseExpand() throws IOException + { + int l_rCode; + int l_byteCount; + + do + { + l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, + l_byteCount, + m_responseMessage ); + + if ( l_rCode >= 400 ) + { + try + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + catch ( SMTPServerException e ) + { + while( '-' == (char)m_response[3] ) + { + m_io.readLine( m_response ); + } + throw e; + } + } + else + { + m_notifySink.expand( l_rCode, m_responseMessage ); + } + } + while( '-' == (char)m_response[3] ); + + m_notifySink.expandComplete(); + } + + private final void parseHelp() throws IOException + { + int l_rCode; + int l_byteCount; + + do + { + l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, + l_byteCount, + m_responseMessage ); + + if ( l_rCode >= 400 ) + { + try + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + catch ( SMTPServerException e ) + { + while( '-' == (char)m_response[3] ) + { + m_io.readLine( m_response ); + } + throw e; + } + } + else + { + m_notifySink.help( l_rCode, m_responseMessage ); + } + } + while( '-' == (char)m_response[3] ); + + m_notifySink.helpComplete(); + } + + private final void parseMail() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.mailFrom( l_rCode, m_responseMessage ); + } + } + + private final void parseNoop() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.noop( l_rCode, m_responseMessage ); + } + } + + private final void parseQuit() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.quit( l_rCode, m_responseMessage ); + } + } + + private final void parseRcpt() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.rcptTo( l_rCode, m_responseMessage ); + } + } + + private final void parseReset() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.reset( l_rCode, m_responseMessage ); + } + } + + private final void parseSend() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.send( l_rCode, m_responseMessage ); + } + } + + private final void parseSendCommand() throws IOException + { + int l_rCode; + int l_byteCount; + + do + { + l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, + l_byteCount, + m_responseMessage ); + + if ( l_rCode >= 400 ) + { + try + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + catch ( SMTPServerException e ) + { + while( '-' == (char)m_response[3] ) + { + m_io.readLine( m_response ); + } + throw e; + } + } + else + { + m_notifySink.sendCommand( l_rCode, m_responseMessage ); + } + } + while( '-' == (char)m_response[3] ); + + m_notifySink.sendCommandComplete(); + } + + private final void parseVerify() throws IOException + { + int l_rCode; + int l_byteCount = m_io.readLine( m_response ); + + l_rCode = setStatusInfo( m_response, l_byteCount, m_responseMessage ); + + if ( l_rCode >= 400 ) + { + m_notifySink.error( l_rCode, m_responseMessage ); + } + else + { + m_notifySink.verify( l_rCode, m_responseMessage ); + } + } +} diff --git a/msgsdk/java/protocol/SMTP/src/SMTPException.java b/msgsdk/java/protocol/SMTP/src/SMTPException.java new file mode 100644 index 00000000000..94137b04cd0 --- /dev/null +++ b/msgsdk/java/protocol/SMTP/src/SMTPException.java @@ -0,0 +1,55 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +package netscape.messaging.smtp; + +import java.io.*; + +/** +*The SMTPException class represents an internal error in the SMTP +*implementation of the Messaging Access SDK. +*@author derekt@netscape.com +*@version 1.0 +*/ + +public class SMTPException extends IOException +{ + /** + *Creates an SMTPException object. + *Default constructor for the SMTPException class. + */ + public SMTPException() + { + } + + /** + *Creates an SMTPException object that includes a descriptive string. + *@param msg String that describes the exception. + */ + public SMTPException(String msg) + { + super(msg); + } +} diff --git a/msgsdk/java/protocol/SMTP/src/SMTPServerException.java b/msgsdk/java/protocol/SMTP/src/SMTPServerException.java new file mode 100644 index 00000000000..e1f48555d8f --- /dev/null +++ b/msgsdk/java/protocol/SMTP/src/SMTPServerException.java @@ -0,0 +1,67 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +package netscape.messaging.smtp; + +import java.io.*; + +/** +*The SMTPServerException class represents a server response +*error in the SMTP implementation of the Messaging Access SDK. +*

The SMTPServerException class handles server response errors. +*An SMTPServerException is thrown only from the error +*callback on the response sink. The interface definition for +*ISMTPSink states that an SMTPServerException can be thrown, +*but it is up to the developer to determine whether or not +*the implementation of the error callback will throw this +*exception. As a default, the SMTPSink class throws an exception whenever +*the error callback is called. +*

This exception is caused when the server sends an +*error saying that some part of the operation failed or is not +*supported. This can happen even when all relevant code executes +*properly. +*@author derekt@netscape.com +*@version 1.0 +*/ + +public class SMTPServerException extends SMTPException +{ + /** + *Creates an SMTPServerException object. + *Default constructor for a SMTPServerException object. + */ + public SMTPServerException() + { + } + + /** + *Creates an SMTPServerException object that includes a descriptive string. + *@param msg String that describes the exception. + */ + public SMTPServerException(String msg) + { + super(msg); + } +} diff --git a/msgsdk/java/protocol/SMTP/src/SMTPSink.java b/msgsdk/java/protocol/SMTP/src/SMTPSink.java new file mode 100644 index 00000000000..16488c27ab6 --- /dev/null +++ b/msgsdk/java/protocol/SMTP/src/SMTPSink.java @@ -0,0 +1,273 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +package netscape.messaging.smtp; + +/** +*SMTPSink is the default implementation of the response sink for all +*SMTP commands. +*

The SMTPSink object contains callback methods for each client call. +*The client’s processResponses call invokes the appropriate +*object method. +*

The Messaging Access SDK provides the SMTPSink class as a convenience +*implementation the ISMTPSink interface. +*You can save a step by extending the SMTPSink class, or +*you can implement your own class based on the ISMTPSink +*interface. The constructor for the SMTPClient class takes an +*ISMTPSink interface as a parameter. +*

These methods return standard SMTP Response Codes, as defined in RFC 821. +*See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP" +*for more information. For detailed information about SMTP, +*see RFC 821. (For the URL, see "Where to Find More Information" in "About This Book.") +*@author derekt@netscape.com +*@version 1.0 +*/ + +public class SMTPSink implements ISMTPSink +{ + /** + *Notification for the response to the BDAT command. + *Sends binary data chunks of the specified size to the server. + *When using the sendCommand method, send data with the data method + *and not with bdat. + *Note: bdat is not supported by Messaging Server 4.0. Use data instead. + *

See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#bdat + * @see #data + * @see #send + */ + public void bdat( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to the connection to the server. + *

Connects to the server using the default port. + *See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#connect + * @see #quit + */ + public void connect( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to the DATA command. + *Prepares to send data to the server. The sendCommand method requires + *sending data with the data method and not with bdat. + *For more information, see RFC 821 (URL: go to SMTP RFCs). + *See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#data + * @see #bdat + * @see #send + */ + public void data( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to the EHLO command. + *Along with ehloComplete, returns extended server information. + *Can get extended server information (which can be multiline). + *See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_serverInfo Extension supported by the server. + * @see SMTPClient#ehlo + * @see #ehloComplete + */ + public void ehlo( int in_responseCode, StringBuffer in_serverInfo ) {} + + /** + *Notification for the completion of the EHLO command. + *Along with ehlo, returns extended server information. + * @see SMTPClient#ehlo + * @see #ehlo + */ + public void ehloComplete() {} + + /** + *Error notification. + *Called when an error occurs. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_errorMessage Text that describes the error. + * @exception SMTPServerException If a server response error occurs. + * @see SMTPClient#processResponses + */ + public void error( int in_responseCode, StringBuffer in_errorMessage ) throws SMTPServerException + { + throw new SMTPServerException(); + } + + /** + *Notification for the response to the EXPN command. + *Along with expandComplete, gets the email address of the specified user. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_user User for whom to get an email address. + * @see SMTPClient#expand + * @see #expandComplete + */ + public void expand( int in_responseCode, StringBuffer in_user ) {} + + /** + *Notification for the completion of the EXPN command. + *Along with expand, gets the email address of the specified user. + * @see SMTPClient#expand + * @see #expand + */ + public void expandComplete() {} + + /** + *Notification for the response to the HELP command. + *Along with helpComplete, gets help on the specified topic, + *which can be multiline. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_help Line of help text. + * @see SMTPClient#help + * @see #helpComplete + */ + public void help( int in_responseCode, StringBuffer in_help ) {} + + /** + *Notification for the completion of the HELP command. + *Along with help, gets help on a specified topic. + * @see SMTPClient#help + * @see #help + */ + public void helpComplete() {} + + /** + *Notification for the response to the MAIL FROM command. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#mailFrom + * @see #rcptTo + */ + public void mailFrom( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to the NOOP command. + *

The server responds to commands with a "still here" response. + *Sending the noop method does nothing except force this response. + *Can be used to maintain server connection, perhaps being issued + *at timed intervals to make sure that the server is still active. + *

Resets the autologout timer inside the server. + *Not needed by applications that do something and do not maintain the connection. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#noop + */ + public void noop( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification sink for the response to the QUIT command. + *Ends the session. If the session is in the Authentication state, + *the server closes the server connection. If the session is in + *the Transaction state, the server goes into the Update state + *and deletes any marked messages, and then quits. + * See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#quit + */ + public void quit( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to the RCPT TO command. + *Gets the address of the recipient of the message. Called once for each + *recipient; should follow the SMTP_mailFrom function. + * See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#rcptTo + * @see #mailFrom + */ + public void rcptTo( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to the RSET command. + *Cancels the current mail transfer and all current processes, + *discards data, and clears all states. Returns to the state that + *followed the last method that sent the EHLO command. + *Returns a response code and optional response text. + * See "SMTP Response Codes" in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#reset + * @see #ehlo + */ + public void reset( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to data sent to the server. + *Returns a response code and optional response text. + *This method requires using the SMTP_data command to send data, + *rather than SMTP_bdat. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Text that describes the 3-digit Response Code. + * @see SMTPClient#send + * @see #data + */ + public void send( int in_responseCode, StringBuffer in_responseMessage ) {} + + /** + *Notification for the response to sendCommand() method. + *Along with sendCommandComplete, extends the protocol to + *meet client application needs. Sends commands that are not supported + *by the Messaging SDK implementation of SMTP. + *Can get extended server information, possibly multiline. + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseLine Buffer for the response message. + * @see SMTPClient#sendCommand + * @see #sendCommandComplete + */ + public void sendCommand( int in_responseCode, StringBuffer in_responseLine ) {} + + /** + *Notification for the completion of the extended command. + *Along with sendCommand, extends the protocol to meet client application + *needs. + * @see SMTPClient#sendCommand + * @see #sendCommand + */ + public void sendCommandComplete() {} + + /** + *Notification for the response to the VRFY command. + *Returns a response code and optional response text. + *For more information, see RFC 821 (URL are listed in SMTP RFCs). + * See SMTP Response Codes in Chapter 2, "Sending Mail with SMTP." + * @param in_responseCode 3-digit Response Code, as defined in RFC 821, for the response. + * @param in_responseMessage Buffer for the response message. + * @see SMTPClient#verify + */ + public void verify( int in_responseCode, StringBuffer in_responseMessage ) {} +} diff --git a/msgsdk/java/protocol/SMTP/test/SMTPTest.java b/msgsdk/java/protocol/SMTP/test/SMTPTest.java new file mode 100644 index 00000000000..00951e9b10f --- /dev/null +++ b/msgsdk/java/protocol/SMTP/test/SMTPTest.java @@ -0,0 +1,108 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +/* + * Copyright (c) 1997 and 1998 Netscape Communications Corporation + * (http://home.netscape.com/misc/trademarks.html) + */ + +import netscape.messaging.smtp.*; +import java.io.*; +import java.util.*; + +/** +*@author derekt@netscape.com +*@version 1.0 +*/ + +// Usage: At the command line type "java SMTPTest serverName domainName sender recipient messageFile" + +class SMTPTest +{ + public static void main( String args[] ) + { + SMTPClient m_client; + ISMTPSink m_smtpSink; + + try + { + // Create the response sink. + m_smtpSink = new SMTPTestSink(); + + // Create the SMTP client. + m_client = new SMTPClient( m_smtpSink ); + + // Set a timeout value of 10 seconds. + m_client.setTimeout( 10000 ); + + // Set a new chunk size. + m_client.setChunkSize( 1048576 ); + + // Connect to the specified server. + m_client.connect( args[0] ); + m_client.processResponses(); + + // Send the EHLO command. + m_client.ehlo( args[1] ); + m_client.processResponses(); + + // Specify the sender of the message. + m_client.mailFrom( args[2], null ); + m_client.processResponses(); + + // Specify the recipient of the message. + m_client.rcptTo( args[3], null ); + m_client.processResponses(); + + // Inform the server that data is about to be sent. + m_client.data(); + m_client.processResponses(); + + // Send the file based message. + m_client.send( new FileInputStream( args[4] ) ); + m_client.processResponses(); + + // Ask for help on helpSend the file based message. + m_client.help( "help" ); + m_client.processResponses(); + + // Perform a noopsk for help on helpSend the file based message. + m_client.noop(); + m_client.processResponses(); + + // Reset the state of the server. + m_client.reset(); + m_client.processResponses(); + + // Send a generic command to the server. In this case it is RSET. + m_client.sendCommand( "RSET" ); + m_client.processResponses(); + + // End the SMTP session. + m_client.quit(); + m_client.processResponses(); + } + catch( IOException e ) + { + // Print out the exception. + System.out.println( "Exception caught in main:" + e ); + } + } +} diff --git a/msgsdk/java/protocol/SMTP/test/SMTPTestSink.java b/msgsdk/java/protocol/SMTP/test/SMTPTestSink.java new file mode 100644 index 00000000000..b703a8878f9 --- /dev/null +++ b/msgsdk/java/protocol/SMTP/test/SMTPTestSink.java @@ -0,0 +1,195 @@ +/* + * The contents of this file are subject to the Netscape Public License + * Version 1.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the License. + * + * The Original Code is the Netscape Messaging Access SDK Version 3.5 code, + * released on or about June 15, 1998. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are Copyright (C) 1998 Netscape + * Communications Corporation. All Rights Reserved. + * + * Contributor(s): ______________________________________. +*/ + +import netscape.messaging.smtp.*; +import java.util.Vector; + +/** +*Notification sink for SMTP commands. +*@author derekt@netscape.com +*@version 1.0 +*/ + +public class SMTPTestSink implements ISMTPSink +{ + Vector m_extendedList; + + SMTPTestSink() + { + } + + /** + *Notification for the response to the BDAT command + */ + public void bdat( int in_responseCode, StringBuffer in_responseMessage ) + { + }; + + /** + *Notification for the response to the connection to the server. + */ + public void connect( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification for the response to the DATA command. + */ + public void data( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification for the response to the EHLO command. + */ + public void ehlo( int in_responseCode, StringBuffer in_serverInfo ) + { + if ( m_extendedList == null ) + { + m_extendedList = new Vector(); + } + + m_extendedList.addElement( in_serverInfo.toString() ); + + System.out.println( (new Integer(in_responseCode)) + " " + (in_serverInfo.toString()) ); + } + + /** + *Notification for the completion of the EHLO command. + */ + public void ehloComplete() + { + System.out.println( "EHLO Complete" ); + } + + /** + *Notification for the response to a server error. + */ + public void error( int in_responseCode, StringBuffer in_errorMessage ) throws SMTPServerException + { + throw new SMTPServerException( in_errorMessage.toString() ); + } + + /** + *Notification for the response to the EXPN command. + */ + public void expand( int in_responseCode, StringBuffer in_user ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_user.toString()) ); + } + + /** + *Notification for the completion of the EXPN command. + */ + public void expandComplete() + { + System.out.println( "EXPAND Complete" ); + } + + /** + *Notification for the response to the HELP command. + */ + public void help( int in_responseCode, StringBuffer in_help ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_help.toString()) ); + } + + /** + *Notification for the completion of the HELP command. + */ + public void helpComplete() + { + System.out.println( "HELP Complete" ); + } + + /** + *Notification for the response to the MAIL FROM command. + */ + public void mailFrom( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification for the response to the NOOP command. + */ + public void noop( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification sink for the response to the QUIT command. + */ + public void quit( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification for the response to the RCPT TO command. + */ + public void rcptTo( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification for the response to the RSET command. + */ + public void reset( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification for the response to data sent to the server. + */ + public void send( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + } + + /** + *Notification for the response to sendCommand() method. + */ + public void sendCommand( int in_responseCode, StringBuffer in_responseLine ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseLine.toString()) ); + }; + + /** + *Notification for the completion of the extended command. + */ + public void sendCommandComplete() + { + System.out.println( "SENDCOMMAND Complete" ); + }; + + /** + *Notification for the response to the VRFY command. + */ + public void verify( int in_responseCode, StringBuffer in_responseMessage ) + { + System.out.println( (new Integer(in_responseCode)) + " " + (in_responseMessage.toString()) ); + }; +}