Skip to content

Commit 1659221

Browse files
committed
[test] Add a suite of tests for MIME emails
1 parent b1eaf67 commit 1659221

File tree

5 files changed

+1338
-76
lines changed

5 files changed

+1338
-76
lines changed

extensions/modules/mail/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
<header>${project.parent.relativePath}/LGPL-21-license.template.txt</header>
175175
<excludes>
176176
<exclude>src/test/java/org/exist/xquery/modules/mail/SendEmailIT.java</exclude>
177+
<exclude>src/test/java/org/exist/xquery/modules/mail/WriteMessageTest.java</exclude>
177178
<exclude>src/test/java/org/exist/xquery/modules/mail/Util.java</exclude>
178179
</excludes>
179180
</licenseSet>
@@ -185,6 +186,7 @@
185186
<header>${project.parent.relativePath}/FDB-backport-LGPL-21-ONLY-license.template.txt</header>
186187
<includes>
187188
<include>src/test/java/org/exist/xquery/modules/mail/SendEmailIT.java</include>
189+
<include>src/test/java/org/exist/xquery/modules/mail/WriteMessageTest.java</include>
188190
<include>src/test/java/org/exist/xquery/modules/mail/Util.java</include>
189191
</includes>
190192

extensions/modules/mail/src/main/java/org/exist/xquery/modules/mail/SendEmailFunction.java

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public class SendEmailFunction extends BasicFunction {
7676
*/
7777
private static final Pattern NON_TOKEN_PATTERN = Pattern.compile("^.*[\\s\\p{Cntrl}()<>@,;:\\\"/\\[\\]?=].*$");
7878

79-
private String charset;
79+
static final String ERROR_MSG_NON_MIME_CLIENT = "Error your mail client is not MIME Compatible";
8080

8181
public final static FunctionSignature deprecated = new FunctionSignature(
8282
new QName("send-email", MailModule.NAMESPACE_URI, MailModule.PREFIX),
@@ -161,6 +161,7 @@ public Sequence sendEmail(final Sequence[] args, final Sequence contextSequence)
161161
public Sequence deprecatedSendEmail(final Sequence[] args, final Sequence contextSequence) throws XPathException {
162162
try {
163163
//get the charset parameter, default to UTF-8
164+
final String charset;
164165
if (!args[2].isEmpty()) {
165166
charset = args[2].getStringValue();
166167
} else {
@@ -180,14 +181,14 @@ public Sequence deprecatedSendEmail(final Sequence[] args, final Sequence contex
180181
//Send email with Sendmail or SMTP?
181182
if (!args[1].isEmpty()) {
182183
//SMTP
183-
final boolean[] mailResults = sendBySMTP(mails, args[1].getStringValue());
184+
final boolean[] mailResults = sendBySMTP(mails, args[1].getStringValue(), charset);
184185

185186
for (final boolean mailResult : mailResults) {
186187
results.add(BooleanValue.valueOf(mailResult));
187188
}
188189
} else {
189190
for (final Mail mail : mails) {
190-
final boolean result = sendBySendmail(mail);
191+
final boolean result = sendBySendmail(mail, charset);
191192
results.add(BooleanValue.valueOf(result));
192193
}
193194
}
@@ -214,9 +215,10 @@ private Message[] parseInputEmails(final Session session, final Sequence arg) th
214215
* Sends an email using the Operating Systems sendmail application
215216
*
216217
* @param mail representation of the email to send
218+
* @param charset the character set
217219
* @return boolean value of true of false indicating success or failure to send email
218220
*/
219-
private boolean sendBySendmail(final Mail mail) {
221+
private boolean sendBySendmail(final Mail mail, final String charset) {
220222

221223
//Create a list of all Recipients, should include to, cc and bcc recipient
222224
final List<String> allrecipients = new ArrayList<>();
@@ -247,7 +249,7 @@ private boolean sendBySendmail(final Mail mail) {
247249
//Get a Buffered Print Writer to the Processes stdOut
248250
try (final PrintWriter out = new PrintWriter(new OutputStreamWriter(p.getOutputStream(), charset))) {
249251
//Send the Message
250-
writeMessage(out, mail, false);
252+
writeMessage(out, mail, false, charset);
251253
}
252254
} catch (final IOException e) {
253255
LOGGER.error(e.getMessage(), e);
@@ -279,10 +281,11 @@ public SMTPException(final Throwable cause) {
279281
*
280282
* @param mails A list of mail object representing the email to send
281283
* @param smtpServerArg The SMTP Server to send the email through
284+
* @param charset the character set
282285
* @return boolean value of true of false indicating success or failure to send email
283286
* @throws SMTPException if an I/O error occurs
284287
*/
285-
private boolean[] sendBySMTP(final Mail[] mails, final String smtpServerArg) throws SMTPException {
288+
private boolean[] sendBySMTP(final Mail[] mails, final String smtpServerArg, final String charset) throws SMTPException {
286289
String smtpHost = "localhost";
287290
int smtpPort = 25;
288291

@@ -338,7 +341,7 @@ private boolean[] sendBySMTP(final Mail[] mails, final String smtpServerArg) thr
338341

339342
//write SMTP message(s)
340343
for (int i = 0; i < mails.length; i++) {
341-
final boolean mailResult = writeSMTPMessage(mails[i], smtpOut, smtpIn);
344+
final boolean mailResult = writeSMTPMessage(mails[i], smtpOut, smtpIn, charset);
342345
sendMailResults[i] = mailResult;
343346
}
344347

@@ -355,7 +358,7 @@ private boolean[] sendBySMTP(final Mail[] mails, final String smtpServerArg) thr
355358
return sendMailResults;
356359
}
357360

358-
private boolean writeSMTPMessage(final Mail mail, final PrintWriter smtpOut, final BufferedReader smtpIn) {
361+
private boolean writeSMTPMessage(final Mail mail, final PrintWriter smtpOut, final BufferedReader smtpIn, final String charset) {
359362
try {
360363
String smtpResult;
361364

@@ -421,7 +424,7 @@ private boolean writeSMTPMessage(final Mail mail, final PrintWriter smtpOut, fin
421424
}
422425

423426
//Send the Message
424-
writeMessage(smtpOut, mail, true);
427+
writeMessage(smtpOut, mail, true, charset);
425428

426429
//Get end message response, should be "250 blah blah"
427430
smtpResult = smtpIn.readLine();
@@ -438,59 +441,59 @@ private boolean writeSMTPMessage(final Mail mail, final PrintWriter smtpOut, fin
438441
}
439442

440443
/**
441-
* Writes an email payload (Headers + Body) from a mail object
444+
* Writes an email payload (Headers + Body) from a mail object.
445+
*
446+
* Access is package-private for unit testing purposes.
442447
*
443448
* @param out A PrintWriter to receive the email
444449
* @param aMail A mail object representing the email to write out
445450
* @param useCrLf true to use CRLF for line ending, false to use LF
451+
* @param charset the character set
446452
* @throws IOException if an I/O error occurs
447453
*/
448-
private void writeMessage(final PrintWriter out, final Mail aMail, final boolean useCrLf) throws IOException {
449-
final String version = Version.getVersion(); //Version of eXist
450-
final String MultipartBoundary = "eXist.multipart." + version; //Multipart Boundary
451-
454+
static void writeMessage(final PrintWriter out, final Mail aMail, final boolean useCrLf, final String charset) throws IOException {
452455
final String eol = useCrLf ? "\r\n" : "\n";
453456

454457
//write the message headers
455458

456-
out.print("From: " + encode64Address(aMail.getFrom()) + eol);
459+
out.print("From: " + encode64Address(aMail.getFrom(), charset) + eol);
457460

458461
if (aMail.getReplyTo() != null) {
459-
out.print("Reply-To: " + encode64Address(aMail.getReplyTo()) + eol);
462+
out.print("Reply-To: " + encode64Address(aMail.getReplyTo(), charset) + eol);
460463
}
461464

462465
for (int x = 0; x < aMail.countTo(); x++) {
463-
out.print("To: " + encode64Address(aMail.getTo(x)) + eol);
466+
out.print("To: " + encode64Address(aMail.getTo(x), charset) + eol);
464467
}
465468

466469
for (int x = 0; x < aMail.countCC(); x++) {
467-
out.print("CC: " + encode64Address(aMail.getCC(x)) + eol);
470+
out.print("CC: " + encode64Address(aMail.getCC(x), charset) + eol);
468471
}
469472

470473
for (int x = 0; x < aMail.countBCC(); x++) {
471-
out.print("BCC: " + encode64Address(aMail.getBCC(x)) + eol);
474+
out.print("BCC: " + encode64Address(aMail.getBCC(x), charset) + eol);
472475
}
473476

474477
out.print("Date: " + getDateRFC822() + eol);
475478
String subject = aMail.getSubject();
476479
if (subject == null) {
477480
subject = "";
478481
}
479-
out.print("Subject: " + encode64(subject) + eol);
480-
out.print("X-Mailer: eXist " + version + " mail:send-email()" + eol);
482+
out.print("Subject: " + encode64(subject, charset) + eol);
483+
out.print("X-Mailer: eXist-db " + Version.getVersion() + " mail:send-email()" + eol);
481484
out.print("MIME-Version: 1.0" + eol);
482485

483486

484487
boolean multipartAlternative = false;
485488
String multipartBoundary = null;
486489

487490
if (aMail.attachmentIterator().hasNext()) {
488-
// we have an attachment as well as text and/or html so we need a multipart/mixed message
489-
multipartBoundary = MultipartBoundary;
491+
// we have an attachment as well as text and/or html, so we need a multipart/mixed message
492+
multipartBoundary = multipartBoundary(1);
490493
} else if (nonEmpty(aMail.getText()) && nonEmpty(aMail.getXHTML())) {
491494
// we have text and html, so we need a multipart/alternative message and no attachment
492495
multipartAlternative = true;
493-
multipartBoundary = MultipartBoundary + "_alt";
496+
multipartBoundary = multipartBoundary(1) + "_alt";
494497
}
495498
// else {
496499
// // we have either text or html and no attachment this message is not multipart
@@ -500,19 +503,19 @@ private void writeMessage(final PrintWriter out, final Mail aMail, final boolean
500503
if (multipartBoundary != null) {
501504
//multipart message
502505

503-
out.print("Content-Type: " + (multipartAlternative ? "multipart/alternative" : "multipart/mixed") + "; boundary=\"" + multipartBoundary + "\";" + eol);
506+
out.print("Content-Type: " + (multipartAlternative ? "multipart/alternative" : "multipart/mixed") + "; boundary=" + parameterValue(multipartBoundary) + eol);
504507

505508
//Mime warning
506509
out.print(eol);
507-
out.print("Error your mail client is not MIME Compatible" + eol);
510+
out.print(ERROR_MSG_NON_MIME_CLIENT + eol);
508511

509512
out.print("--" + multipartBoundary + eol);
510513
}
511514

512515
// TODO - need to put out a multipart/mixed boundary here when HTML, text and attachment present
513516
if (nonEmpty(aMail.getText()) && nonEmpty(aMail.getXHTML()) && aMail.attachmentIterator().hasNext()) {
514-
out.print("Content-Type: multipart/alternative; boundary=\"" + MultipartBoundary + "_alt\";" + eol);
515-
out.print("--" + MultipartBoundary + "_alt" + eol);
517+
out.print("Content-Type: multipart/alternative; boundary=" + parameterValue(multipartBoundary(1) + "_alt") + eol);
518+
out.print("--" + multipartBoundary(1) + "_alt" + eol);
516519
}
517520

518521
//text email
@@ -527,13 +530,13 @@ private void writeMessage(final PrintWriter out, final Mail aMail, final boolean
527530
if (multipartBoundary != null) {
528531
if (nonEmpty(aMail.getXHTML()) || aMail.attachmentIterator().hasNext()) {
529532
if (nonEmpty(aMail.getText()) && nonEmpty(aMail.getXHTML()) && aMail.attachmentIterator().hasNext()) {
530-
out.print("--" + MultipartBoundary + "_alt" + eol);
533+
out.print("--" + multipartBoundary(1) + "_alt" + eol);
531534
} else {
532535
out.print("--" + multipartBoundary + eol);
533536
}
534537
} else {
535538
if (nonEmpty(aMail.getText()) && nonEmpty(aMail.getXHTML()) && aMail.attachmentIterator().hasNext()) {
536-
out.print("--" + MultipartBoundary + "_alt--" + eol);
539+
out.print("--" + multipartBoundary(1) + "_alt--" + eol);
537540
} else {
538541
out.print("--" + multipartBoundary + "--" + eol);
539542
}
@@ -554,14 +557,14 @@ private void writeMessage(final PrintWriter out, final Mail aMail, final boolean
554557
if (multipartBoundary != null) {
555558
if (aMail.attachmentIterator().hasNext()) {
556559
if (nonEmpty(aMail.getText()) && nonEmpty(aMail.getXHTML()) && aMail.attachmentIterator().hasNext()) {
557-
out.print("--" + MultipartBoundary + "_alt--" + eol);
560+
out.print("--" + multipartBoundary(1) + "_alt--" + eol);
558561
out.print("--" + multipartBoundary + eol);
559562
} else {
560563
out.print("--" + multipartBoundary + eol);
561564
}
562565
} else {
563566
if (nonEmpty(aMail.getText()) && nonEmpty(aMail.getXHTML()) && aMail.attachmentIterator().hasNext()) {
564-
out.print("--" + MultipartBoundary + "_alt--" + eol);
567+
out.print("--" + multipartBoundary(1) + "_alt--" + eol);
565568
} else {
566569
out.print("--" + multipartBoundary + "--" + eol);
567570
}
@@ -951,7 +954,7 @@ private Message[] parseMessageElement(final Session session, final Element[] mai
951954
*
952955
* @return RFC822 formated date and time as a String
953956
*/
954-
private String getDateRFC822() {
957+
private static String getDateRFC822() {
955958
String dateString = "";
956959
final Calendar rightNow = Calendar.getInstance();
957960

@@ -1116,13 +1119,15 @@ private String getDateRFC822() {
11161119
}
11171120

11181121
/**
1119-
* Base64 Encodes a string (used for message subject)
1122+
* Base64 Encodes a string (used for message subject).
1123+
*
1124+
* Access is package-private for unit testing purposes.
11201125
*
11211126
* @param str The String to encode
11221127
* @throws java.io.UnsupportedEncodingException if the encocding is unsupported
11231128
* @return The encoded String
11241129
*/
1125-
private String encode64(final String str) throws java.io.UnsupportedEncodingException {
1130+
static String encode64(final String str, final String charset) throws java.io.UnsupportedEncodingException {
11261131
String result = Base64.encodeBase64String(str.getBytes(charset));
11271132
result = result.replaceAll("\n", "?=\n =?" + charset + "?B?");
11281133
result = "=?" + charset + "?B?" + result + "?=";
@@ -1133,15 +1138,16 @@ private String encode64(final String str) throws java.io.UnsupportedEncodingExce
11331138
* Base64 Encodes an email address
11341139
*
11351140
* @param str The email address as a String to encode
1141+
* @param charset the character set
11361142
* @throws java.io.UnsupportedEncodingException if the encocding is unsupported
11371143
* @return The encoded email address String
11381144
*/
1139-
private String encode64Address(final String str) throws java.io.UnsupportedEncodingException {
1145+
private static String encode64Address(final String str, final String charset) throws java.io.UnsupportedEncodingException {
11401146
final int idx = str.indexOf("<");
11411147

11421148
final String result;
11431149
if (idx != -1) {
1144-
result = encode64(str.substring(0, idx)) + " " + str.substring(idx);
1150+
result = encode64(str.substring(0, idx), charset) + " " + str.substring(idx);
11451151
} else {
11461152
result = str;
11471153
}
@@ -1150,13 +1156,16 @@ private String encode64Address(final String str) throws java.io.UnsupportedEncod
11501156
}
11511157

11521158
/**
1153-
* A simple data class to represent an email
1154-
* attachment. Just has private
1155-
* members and some get methods.
1159+
* A simple data class to represent an email attachment.
1160+
*
1161+
* It doesn't do anything fancy, it just has private
1162+
* members and get and set methods.
1163+
*
1164+
* Access is package-private for unit testing purposes.
11561165
*
11571166
* @version 1.2
11581167
*/
1159-
private static class MailAttachment {
1168+
static class MailAttachment {
11601169
private final String filename;
11611170
private final String mimeType;
11621171
private final String data;
@@ -1181,13 +1190,15 @@ public String getMimeType() {
11811190
}
11821191

11831192
/**
1184-
* A simple data class to represent an email
1185-
* doesnt do anything fancy just has private
1186-
* members and get and set methods
1193+
* A simple data class to represent an email.
1194+
* It doesn't do anything fancy, it just has private
1195+
* members and get and set methods.
1196+
*
1197+
* Access is package-private for unit testing purposes.
11871198
*
11881199
* @version 1.2
11891200
*/
1190-
private static class Mail {
1201+
static class Mail {
11911202
private String from; //Who is the mail from
11921203
private String replyTo; //Who should you reply to
11931204
private final List<String> to = new ArrayList<>(1); //Who is the mail going to
@@ -1342,11 +1353,13 @@ private static boolean nonEmpty(@Nullable final String str) {
13421353
* if it contains a non-token value (See {@link #isNonToken(String)}),
13431354
* otherwise it returns the parameter value as is.
13441355
*
1356+
* Access is package-private for unit testing purposes.
1357+
*
13451358
* @param value parameter value.
13461359
*
13471360
* @return the quoted string parameter value, or the parameter value as is.
13481361
*/
1349-
private static String parameterValue(final String value) {
1362+
static String parameterValue(final String value) {
13501363
if (isNonToken(value)) {
13511364
return "\"" + value + "\"";
13521365
} else {
@@ -1365,4 +1378,19 @@ private static String parameterValue(final String value) {
13651378
private static boolean isNonToken(final String str) {
13661379
return NON_TOKEN_PATTERN.matcher(str).matches();
13671380
}
1381+
1382+
/**
1383+
* Produce a multi-part boundary string.
1384+
*
1385+
* Access is package-private for unit testing purposes.
1386+
*
1387+
* @param multipartInstance the number of this multipart instance.
1388+
*
1389+
* @return the multi-part boundary string.
1390+
*/
1391+
static String multipartBoundary(final int multipartInstance) {
1392+
// Get the version of eXist-db
1393+
final String version = Version.getVersion();
1394+
return "eXist-db.multipart." + version + "_multipart_" + multipartInstance;
1395+
}
13681396
}

0 commit comments

Comments
 (0)