@@ -76,7 +76,7 @@ public class SendEmailFunction extends BasicFunction {
76
76
*/
77
77
private static final Pattern NON_TOKEN_PATTERN = Pattern .compile ("^.*[\\ s\\ p{Cntrl}()<>@,;:\\ \" /\\ [\\ ]?=].*$" );
78
78
79
- private String charset ;
79
+ static final String ERROR_MSG_NON_MIME_CLIENT = "Error your mail client is not MIME Compatible" ;
80
80
81
81
public final static FunctionSignature deprecated = new FunctionSignature (
82
82
new QName ("send-email" , MailModule .NAMESPACE_URI , MailModule .PREFIX ),
@@ -161,6 +161,7 @@ public Sequence sendEmail(final Sequence[] args, final Sequence contextSequence)
161
161
public Sequence deprecatedSendEmail (final Sequence [] args , final Sequence contextSequence ) throws XPathException {
162
162
try {
163
163
//get the charset parameter, default to UTF-8
164
+ final String charset ;
164
165
if (!args [2 ].isEmpty ()) {
165
166
charset = args [2 ].getStringValue ();
166
167
} else {
@@ -180,14 +181,14 @@ public Sequence deprecatedSendEmail(final Sequence[] args, final Sequence contex
180
181
//Send email with Sendmail or SMTP?
181
182
if (!args [1 ].isEmpty ()) {
182
183
//SMTP
183
- final boolean [] mailResults = sendBySMTP (mails , args [1 ].getStringValue ());
184
+ final boolean [] mailResults = sendBySMTP (mails , args [1 ].getStringValue (), charset );
184
185
185
186
for (final boolean mailResult : mailResults ) {
186
187
results .add (BooleanValue .valueOf (mailResult ));
187
188
}
188
189
} else {
189
190
for (final Mail mail : mails ) {
190
- final boolean result = sendBySendmail (mail );
191
+ final boolean result = sendBySendmail (mail , charset );
191
192
results .add (BooleanValue .valueOf (result ));
192
193
}
193
194
}
@@ -214,9 +215,10 @@ private Message[] parseInputEmails(final Session session, final Sequence arg) th
214
215
* Sends an email using the Operating Systems sendmail application
215
216
*
216
217
* @param mail representation of the email to send
218
+ * @param charset the character set
217
219
* @return boolean value of true of false indicating success or failure to send email
218
220
*/
219
- private boolean sendBySendmail (final Mail mail ) {
221
+ private boolean sendBySendmail (final Mail mail , final String charset ) {
220
222
221
223
//Create a list of all Recipients, should include to, cc and bcc recipient
222
224
final List <String > allrecipients = new ArrayList <>();
@@ -247,7 +249,7 @@ private boolean sendBySendmail(final Mail mail) {
247
249
//Get a Buffered Print Writer to the Processes stdOut
248
250
try (final PrintWriter out = new PrintWriter (new OutputStreamWriter (p .getOutputStream (), charset ))) {
249
251
//Send the Message
250
- writeMessage (out , mail , false );
252
+ writeMessage (out , mail , false , charset );
251
253
}
252
254
} catch (final IOException e ) {
253
255
LOGGER .error (e .getMessage (), e );
@@ -279,10 +281,11 @@ public SMTPException(final Throwable cause) {
279
281
*
280
282
* @param mails A list of mail object representing the email to send
281
283
* @param smtpServerArg The SMTP Server to send the email through
284
+ * @param charset the character set
282
285
* @return boolean value of true of false indicating success or failure to send email
283
286
* @throws SMTPException if an I/O error occurs
284
287
*/
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 {
286
289
String smtpHost = "localhost" ;
287
290
int smtpPort = 25 ;
288
291
@@ -338,7 +341,7 @@ private boolean[] sendBySMTP(final Mail[] mails, final String smtpServerArg) thr
338
341
339
342
//write SMTP message(s)
340
343
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 );
342
345
sendMailResults [i ] = mailResult ;
343
346
}
344
347
@@ -355,7 +358,7 @@ private boolean[] sendBySMTP(final Mail[] mails, final String smtpServerArg) thr
355
358
return sendMailResults ;
356
359
}
357
360
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 ) {
359
362
try {
360
363
String smtpResult ;
361
364
@@ -421,7 +424,7 @@ private boolean writeSMTPMessage(final Mail mail, final PrintWriter smtpOut, fin
421
424
}
422
425
423
426
//Send the Message
424
- writeMessage (smtpOut , mail , true );
427
+ writeMessage (smtpOut , mail , true , charset );
425
428
426
429
//Get end message response, should be "250 blah blah"
427
430
smtpResult = smtpIn .readLine ();
@@ -438,59 +441,59 @@ private boolean writeSMTPMessage(final Mail mail, final PrintWriter smtpOut, fin
438
441
}
439
442
440
443
/**
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.
442
447
*
443
448
* @param out A PrintWriter to receive the email
444
449
* @param aMail A mail object representing the email to write out
445
450
* @param useCrLf true to use CRLF for line ending, false to use LF
451
+ * @param charset the character set
446
452
* @throws IOException if an I/O error occurs
447
453
*/
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 {
452
455
final String eol = useCrLf ? "\r \n " : "\n " ;
453
456
454
457
//write the message headers
455
458
456
- out .print ("From: " + encode64Address (aMail .getFrom ()) + eol );
459
+ out .print ("From: " + encode64Address (aMail .getFrom (), charset ) + eol );
457
460
458
461
if (aMail .getReplyTo () != null ) {
459
- out .print ("Reply-To: " + encode64Address (aMail .getReplyTo ()) + eol );
462
+ out .print ("Reply-To: " + encode64Address (aMail .getReplyTo (), charset ) + eol );
460
463
}
461
464
462
465
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 );
464
467
}
465
468
466
469
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 );
468
471
}
469
472
470
473
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 );
472
475
}
473
476
474
477
out .print ("Date: " + getDateRFC822 () + eol );
475
478
String subject = aMail .getSubject ();
476
479
if (subject == null ) {
477
480
subject = "" ;
478
481
}
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 );
481
484
out .print ("MIME-Version: 1.0" + eol );
482
485
483
486
484
487
boolean multipartAlternative = false ;
485
488
String multipartBoundary = null ;
486
489
487
490
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 ) ;
490
493
} else if (nonEmpty (aMail .getText ()) && nonEmpty (aMail .getXHTML ())) {
491
494
// we have text and html, so we need a multipart/alternative message and no attachment
492
495
multipartAlternative = true ;
493
- multipartBoundary = MultipartBoundary + "_alt" ;
496
+ multipartBoundary = multipartBoundary ( 1 ) + "_alt" ;
494
497
}
495
498
// else {
496
499
// // 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
500
503
if (multipartBoundary != null ) {
501
504
//multipart message
502
505
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 );
504
507
505
508
//Mime warning
506
509
out .print (eol );
507
- out .print ("Error your mail client is not MIME Compatible" + eol );
510
+ out .print (ERROR_MSG_NON_MIME_CLIENT + eol );
508
511
509
512
out .print ("--" + multipartBoundary + eol );
510
513
}
511
514
512
515
// TODO - need to put out a multipart/mixed boundary here when HTML, text and attachment present
513
516
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 );
516
519
}
517
520
518
521
//text email
@@ -527,13 +530,13 @@ private void writeMessage(final PrintWriter out, final Mail aMail, final boolean
527
530
if (multipartBoundary != null ) {
528
531
if (nonEmpty (aMail .getXHTML ()) || aMail .attachmentIterator ().hasNext ()) {
529
532
if (nonEmpty (aMail .getText ()) && nonEmpty (aMail .getXHTML ()) && aMail .attachmentIterator ().hasNext ()) {
530
- out .print ("--" + MultipartBoundary + "_alt" + eol );
533
+ out .print ("--" + multipartBoundary ( 1 ) + "_alt" + eol );
531
534
} else {
532
535
out .print ("--" + multipartBoundary + eol );
533
536
}
534
537
} else {
535
538
if (nonEmpty (aMail .getText ()) && nonEmpty (aMail .getXHTML ()) && aMail .attachmentIterator ().hasNext ()) {
536
- out .print ("--" + MultipartBoundary + "_alt--" + eol );
539
+ out .print ("--" + multipartBoundary ( 1 ) + "_alt--" + eol );
537
540
} else {
538
541
out .print ("--" + multipartBoundary + "--" + eol );
539
542
}
@@ -554,14 +557,14 @@ private void writeMessage(final PrintWriter out, final Mail aMail, final boolean
554
557
if (multipartBoundary != null ) {
555
558
if (aMail .attachmentIterator ().hasNext ()) {
556
559
if (nonEmpty (aMail .getText ()) && nonEmpty (aMail .getXHTML ()) && aMail .attachmentIterator ().hasNext ()) {
557
- out .print ("--" + MultipartBoundary + "_alt--" + eol );
560
+ out .print ("--" + multipartBoundary ( 1 ) + "_alt--" + eol );
558
561
out .print ("--" + multipartBoundary + eol );
559
562
} else {
560
563
out .print ("--" + multipartBoundary + eol );
561
564
}
562
565
} else {
563
566
if (nonEmpty (aMail .getText ()) && nonEmpty (aMail .getXHTML ()) && aMail .attachmentIterator ().hasNext ()) {
564
- out .print ("--" + MultipartBoundary + "_alt--" + eol );
567
+ out .print ("--" + multipartBoundary ( 1 ) + "_alt--" + eol );
565
568
} else {
566
569
out .print ("--" + multipartBoundary + "--" + eol );
567
570
}
@@ -951,7 +954,7 @@ private Message[] parseMessageElement(final Session session, final Element[] mai
951
954
*
952
955
* @return RFC822 formated date and time as a String
953
956
*/
954
- private String getDateRFC822 () {
957
+ private static String getDateRFC822 () {
955
958
String dateString = "" ;
956
959
final Calendar rightNow = Calendar .getInstance ();
957
960
@@ -1116,13 +1119,15 @@ private String getDateRFC822() {
1116
1119
}
1117
1120
1118
1121
/**
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.
1120
1125
*
1121
1126
* @param str The String to encode
1122
1127
* @throws java.io.UnsupportedEncodingException if the encocding is unsupported
1123
1128
* @return The encoded String
1124
1129
*/
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 {
1126
1131
String result = Base64 .encodeBase64String (str .getBytes (charset ));
1127
1132
result = result .replaceAll ("\n " , "?=\n =?" + charset + "?B?" );
1128
1133
result = "=?" + charset + "?B?" + result + "?=" ;
@@ -1133,15 +1138,16 @@ private String encode64(final String str) throws java.io.UnsupportedEncodingExce
1133
1138
* Base64 Encodes an email address
1134
1139
*
1135
1140
* @param str The email address as a String to encode
1141
+ * @param charset the character set
1136
1142
* @throws java.io.UnsupportedEncodingException if the encocding is unsupported
1137
1143
* @return The encoded email address String
1138
1144
*/
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 {
1140
1146
final int idx = str .indexOf ("<" );
1141
1147
1142
1148
final String result ;
1143
1149
if (idx != -1 ) {
1144
- result = encode64 (str .substring (0 , idx )) + " " + str .substring (idx );
1150
+ result = encode64 (str .substring (0 , idx ), charset ) + " " + str .substring (idx );
1145
1151
} else {
1146
1152
result = str ;
1147
1153
}
@@ -1150,13 +1156,16 @@ private String encode64Address(final String str) throws java.io.UnsupportedEncod
1150
1156
}
1151
1157
1152
1158
/**
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.
1156
1165
*
1157
1166
* @version 1.2
1158
1167
*/
1159
- private static class MailAttachment {
1168
+ static class MailAttachment {
1160
1169
private final String filename ;
1161
1170
private final String mimeType ;
1162
1171
private final String data ;
@@ -1181,13 +1190,15 @@ public String getMimeType() {
1181
1190
}
1182
1191
1183
1192
/**
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.
1187
1198
*
1188
1199
* @version 1.2
1189
1200
*/
1190
- private static class Mail {
1201
+ static class Mail {
1191
1202
private String from ; //Who is the mail from
1192
1203
private String replyTo ; //Who should you reply to
1193
1204
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) {
1342
1353
* if it contains a non-token value (See {@link #isNonToken(String)}),
1343
1354
* otherwise it returns the parameter value as is.
1344
1355
*
1356
+ * Access is package-private for unit testing purposes.
1357
+ *
1345
1358
* @param value parameter value.
1346
1359
*
1347
1360
* @return the quoted string parameter value, or the parameter value as is.
1348
1361
*/
1349
- private static String parameterValue (final String value ) {
1362
+ static String parameterValue (final String value ) {
1350
1363
if (isNonToken (value )) {
1351
1364
return "\" " + value + "\" " ;
1352
1365
} else {
@@ -1365,4 +1378,19 @@ private static String parameterValue(final String value) {
1365
1378
private static boolean isNonToken (final String str ) {
1366
1379
return NON_TOKEN_PATTERN .matcher (str ).matches ();
1367
1380
}
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
+ }
1368
1396
}
0 commit comments