Java Question Modify SMTPWrapper

warwound

Expert
Licensed User
Longtime User
I have been asked to update the Net library SMTP object with a method that will enable various email headers to be set.
Looking at the SMTPWrapper class using Java Decompiler i see in the Send method where the SimpleSMTPHeader class is used.
Ideally the updated SMTP object would have:
  • A new member that is a Map object named additionalHeaders.
    The Map key would be a String headerField and the Map value would be a String headerValue.
  • A new method setHeader(HeaderField As String, HeaderValue As String) which would add a key/value to the additionalHeaders Map object.
  • An updated Send method.
    If the additionalHeaders Map object contains any key/values then it'd iterate over each key/value and add each additional header to the SimpleSMTPHeader instance.

So my options are:
  • Export the SMTPWrapper source code from Java Decompiler, fix the minor errors that occur during the export.
    Make my modifications and recompile.
  • Request the original SMTPWrapper source code, make my modifications and recompile.
  • Create an entirely new library that replicates the existing SMTP object and has the required method to add additional headers.

Option #2 is the quickest solution, option #1 will take just a little more time and option #3 will turn an hour's work into a day's work!

Can i request permission to modify the SMTPWrapper class?
Can i have a copy of the SMTPWrapper source?

I'd add the modified SMTP object to my NetExtras library and share with the forum.

Thanks.

Martin.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Here is the code:
B4X:
package anywheresoftware.b4a.net;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;

import org.apache.commons.net.smtp.AuthenticatingSMTPClient;
import org.apache.commons.net.smtp.SMTPReply;
import org.apache.commons.net.smtp.SimpleSMTPHeader;
import org.apache.commons.net.smtp.AuthenticatingSMTPClient.AUTH_METHOD;
import org.apache.commons.net.util.Base64;
import org.apache.james.mime4j.codec.EncoderUtil;
import org.apache.james.mime4j.codec.EncoderUtil.Encoding;
import org.apache.james.mime4j.codec.EncoderUtil.Usage;

import anywheresoftware.b4a.BA;
import anywheresoftware.b4a.BA.Events;
import anywheresoftware.b4a.BA.Permissions;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.objects.collections.List;
import anywheresoftware.b4a.objects.streams.File;
import anywheresoftware.b4a.objects.streams.File.InputStreamWrapper;
import anywheresoftware.b4a.objects.streams.File.OutputStreamWrapper;

/**
* SMTP object allows you to send emails with no user intervention and without relying on the device installed mail clients.
*Both text messages and Html messages are supported as well as file attachments.
*There are two encryption modes supported: UseSSL and StartTLSMode.
*UseSSL means that the connection will be based on a SSL connection right from the start.
*StartTLSMode means that the connection will only be upgraded to SSL after the client send the STARTTLS command. Most SMTP servers support this mode.
*Gmail for example supports both modes. UseSSL on port 465 and StartTLSMode on port 587.
*
*Example:<code>
*Sub Process_Globals
*   Dim SMTP As SMTP
*End Sub
*Sub Globals
*
*End Sub
*
*Sub Activity_Create(FirstTime As Boolean)
*   If FirstTime Then
*     SMTP.Initialize("smtp.gmail.com", 587, "[email protected]", "mypassword", "SMTP")
*     SMTP.StartTLSMode = True
*   End If
*   SMTP.To.Add("[email protected]")
*   SMTP.Subject = "This is the subject"
*   SMTP.Body = "This is the message body."
*   SMTP.AddAttachment(File.DirRootExternal, "somefile")
*   SMTP.Send
*End Sub
*Sub SMTP_MessageSent(Success As Boolean)
*   Log(Success)
*   If Success Then
*     ToastMessageShow("Message sent successfully", True)
*   Else
*     ToastMessageShow("Error sending message", True)
*     Log(LastException.Message)
*   End If
*End Sub</code>
*/
@ShortName("SMTP")
@Permissions(values = {"android.permission.INTERNET"})
@Events(values = {"MessageSent(Success As Boolean)"})
public class SMTPWrapper {
   private String user, password, server;
   private int port;
   private List to;
   private List cc;
   private List bcc;
   private ArrayList<String[]> attachments ;
   private String body;
   private String subject;
   private static int taskId;
   private String eventName;
   private boolean html;
   private boolean useSSL;
   private boolean startTLSMode;
   /**
    * Initializes the object.
    *Server - Server address. Host name or Ip.
    *Port - Mail server port.
    *Username - Account user name.
    *Password - Account password.
    *EventName - The name of the sub that will handle the MessageSent event.
    */
   public void Initialize(String Server, int Port, String Username, String Password, String EventName) {
     this.user = Username;
     this.password = Password;
     this.server = Server;
     this.port = Port;
     this.eventName = EventName.toLowerCase(BA.cul);
     prepareForNewMessage();
     html = false;
     useSSL = false;
     Sender = Username;

   }
   private void prepareForNewMessage() {
     to = new List();to.Initialize();
     cc = new List();cc.Initialize();
     bcc = new List();bcc.Initialize();
     attachments = new ArrayList<String[]>();
     body = "";
     subject = "";
   }
   /**
    * Gets or sets the Sender field. By default it is the same as the Username.
    */
   public String Sender;
   /**
    * Gets or sets the list of "To" recipients.
    *Example:<code>SMTP.To.Add("[email protected]")</code>
    */
   public void setTo(List To) {
     this.to = To;
   }
   public List getTo() {
     return to;
   }
   /**
    * Gets or sets the list of "CC" recipients.
    */
   public void setCC(List CC) {
     this.cc = CC;
   }
   public List getCC() {
     return cc;
   }
   /**
    * Gets or sets the list of "BCC" recipients.
    */
   public void setBCC(List BCC) {
     this.bcc = BCC;
   }
   public List getBCC() {
     return bcc;
   }
   /**
    * Gets or sets the message body.
    */
   public void setBody(String text)  {
     body = text;
   }
   public String getBody() {
     return body;
   }
   /**
    * Gets or sets the message subject.
    */
   public void setSubject(String text)  {
     subject = text;
   }
   public String getSubject() {
     return subject;
   }
   /**
    * Gets or sets whether this message body is Html text.
    */
   public void setHtmlBody(boolean b) {
     html = b;
   }
   public boolean getHtmlBody() {
     return html;
   }
   /**
    * Gets or sets whether the connection should be done with SSL sockets.
    */
   public void setUseSSL(boolean b) {
     useSSL = b;
   }
   public boolean getUseSSL() {
     return useSSL;
   }
   /**
    * Gets or sets whether the connection should be done in StartTLS mode.
    */
   public void setStartTLSMode(boolean b) {
     startTLSMode = b;
   }
   public boolean getStartTLSMode() {
     return startTLSMode;
   }
   /**
    * Adds a file attachment.
    */
   public void AddAttachment(String Dir, String FileName) {
     attachments.add(new String[] {Dir, FileName});
   }
   /**
    * Sends the message. The MessageSent event will be raised after the message was sent.
    *Note that the message fields are cleared after this method to allow you to send new messages with the same object.
    */
   public void Send(final BA ba) {
     if (to.getSize() == 0)
       throw new RuntimeException("To must include at least one recipient.");
     final List myTo = to, myCC = cc, myBCC = bcc;
     final ArrayList<String[]> myAttachments = attachments;
     final int task = taskId++;
     final String myBody = body, mySubject = subject;
     final boolean myHtml = html;
     prepareForNewMessage();
     Runnable r = new Runnable() {

       @Override
       public void run() {
         synchronized (SMTPWrapper.this) {


           AuthenticatingSMTPClient client = null;
           int reply;
           try {
             client = new AuthenticatingSMTPClient();
             if (useSSL && !startTLSMode)
               client.setSSL();
             client.setDefaultTimeout(60000);
             client.connect(server, port);
             client.setSoTimeout(60000);
             reply = client.getReplyCode();

             if(!SMTPReply.isPositiveCompletion(reply)) {
               client.disconnect();
               throw new IOException("SMTP server refused connection.");
             }


             client.ehlo(client.getLocalAddress().getHostAddress());
             if (startTLSMode)
               client.execTLS();
             client.auth(AUTH_METHOD.PLAIN, user, password);
             client.setSender(user);
             String sub;
             if (EncoderUtil.hasToBeEncoded(mySubject, 0))
               sub = EncoderUtil.encodeEncodedWord(mySubject, Usage.TEXT_TOKEN,
                   0, Charset.forName("UTF8"), Encoding.Q);
             else
               sub = mySubject;
             String encodedSender;
//             if (EncoderUtil.hasToBeEncoded(Sender, 0))
//               encodedSender = EncoderUtil.encodeEncodedWord(Sender, Usage.TEXT_TOKEN, 0, Charset.forName("UTF8"), Encoding.Q);
//             else
               encodedSender = Sender;
             SimpleSMTPHeader header = new SimpleSMTPHeader(encodedSender, sub);
             if (myAttachments.size() == 0) {
               header.addHeaderField("Content-Type", "text/" + (myHtml ? "html" : "plain") + "; charset=\"utf-8\"");
               header.addHeaderField("Content-Transfer-Encoding", "quoted-printable");
             }
             String bound = "asdasdwdwqd__HV_qwdqwdddwq";
             if (myAttachments.size() > 0)
               header.addHeaderField("Content-Type", "multipart/mixed; boundary=\"" + bound + "\"");
             for (int i = 0;i < myTo.getSize();i++) {
               String r = myTo.Get(i).toString();
               client.addRecipient(r);
               header.addTo(r);
             }
             for (int i = 0;i < myCC.getSize();i++) {
               String r = myCC.Get(i).toString();
               client.addRecipient(r);
               header.addCC(r);
             }
             for (int i = 0;i < myBCC.getSize();i++) {
               String r = myBCC.Get(i).toString();
               client.addRecipient(r);
               header.addBCC(r);
             }
             Writer w = client.sendMessageData();
             if (w == null)
               throw new RuntimeException("Empty writer returned: " + client.getReplyString());
             w.append(header.toString());
             if (myAttachments.size() == 0)
               w.append(myBody);
             else {
               w.append("--" + bound + "\r\n");
               w.append("Content-Type: text/$TEXT$; charset=\"utf-8\"\r\n\r\n".replace("$TEXT$", myHtml ? "html" : "plain"));
               header.addHeaderField("Content-Transfer-Encoding", "quoted-printable");
               w.append(myBody);
               for (String[] s : myAttachments) {
                 w.append("\r\n").append("--").append(bound).append("\r\n");
                 w.append("Content-Type: application/octet-stream\r\n");
                 w.append("Content-Transfer-Encoding: base64\r\n");
                 w.append("Content-Disposition: attachment; filename=\"" + s[1] + "\"\r\n\r\n");

                 InputStreamWrapper in = File.OpenInput(s[0], s[1]);
                 OutputStreamWrapper out = new OutputStreamWrapper();
                 out.InitializeToBytesArray(100);
                 File.Copy2(in.getObject(), out.getObject());
                 w.append(Base64.encodeBase64String(out.ToBytesArray()));
                 w.append("\r\n");
               }
             }
             w.close();
             if (client.completePendingCommand() == false)
               throw new RuntimeException("Error sending message.");
             client.quit();
             client.disconnect();
             ba.raiseEventFromDifferentThread(null, SMTPWrapper.this, task,eventName + "_messagesent", false, new Object[]{true});
           } catch(Exception e) {
             e.printStackTrace();
             ba.setLastException(e);
             if(client != null && client.isConnected()) {
               try {
                 client.disconnect();
               } catch(IOException f) {
                 //
               }
             }
             ba.raiseEventFromDifferentThread(null, SMTPWrapper.this, task,eventName + "_messagesent", false, new Object[]{false});

           }

         }
       }
     };
     BA.submitRunnable(r, this, task);
   }
}

You can use it as needed. It will be great if you upload your updated NetExtra library when done.

This code depends on a modified version of the Common Net library. Contact by mail for the full project.
 

warwound

Expert
Licensed User
Longtime User
This code depends on a modified version of the Common Net library. Contact by mail for the full project.

The compiler complains The method execTLS() is undefined for the type AuthenticatingSMTPClient:

B4X:
client.execTLS();

Surely your modified AuthenticatingSMTPClient class is contained in the original Net library?
Though using Java Decompiler i see that AuthenticatingSMTPClient has no execTLS() method and neither does any of it's superclasses.

I'll send an email requesting the modified classes.

Martin.
 

warwound

Expert
Licensed User
Longtime User
I have just uploaded the modified SMTP object as part of my NetExtras library.

Thanks to Erel for providing the original Net library source code.

Martin.
 
Top