Skip to content

Package: MailHandler$GetAndSetContext

MailHandler$GetAndSetContext

nameinstructionbranchcomplexitylinemethod
MailHandler.GetAndSetContext(Object)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
run()
M: 66 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 16 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 2009, 2023 Oracle and/or its affiliates. All rights reserved.
3: * Copyright (c) 2009, 2023 Jason Mehrens. All rights reserved.
4: *
5: * This program and the accompanying materials are made available under the
6: * terms of the Eclipse Public License v. 2.0, which is available at
7: * http://www.eclipse.org/legal/epl-2.0.
8: *
9: * This Source Code may also be made available under the following Secondary
10: * Licenses when the conditions for such availability set forth in the
11: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
12: * version 2 with the GNU Classpath Exception, which is available at
13: * https://www.gnu.org/software/classpath/license.html.
14: *
15: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16: */
17:
18: package org.eclipse.angus.mail.util.logging;
19:
20: import jakarta.activation.DataHandler;
21: import jakarta.activation.DataSource;
22: import jakarta.activation.FileTypeMap;
23: import jakarta.activation.MimetypesFileTypeMap;
24: import jakarta.mail.Address;
25: import jakarta.mail.Authenticator;
26: import jakarta.mail.BodyPart;
27: import jakarta.mail.Message;
28: import jakarta.mail.MessageContext;
29: import jakarta.mail.MessagingException;
30: import jakarta.mail.Part;
31: import jakarta.mail.PasswordAuthentication;
32: import jakarta.mail.SendFailedException;
33: import jakarta.mail.Service;
34: import jakarta.mail.Session;
35: import jakarta.mail.Transport;
36: import jakarta.mail.internet.AddressException;
37: import jakarta.mail.internet.ContentType;
38: import jakarta.mail.internet.InternetAddress;
39: import jakarta.mail.internet.MimeBodyPart;
40: import jakarta.mail.internet.MimeMessage;
41: import jakarta.mail.internet.MimeMultipart;
42: import jakarta.mail.internet.MimePart;
43: import jakarta.mail.internet.MimeUtility;
44: import jakarta.mail.util.ByteArrayDataSource;
45: import jakarta.mail.util.StreamProvider.EncoderTypes;
46:
47: import java.io.ByteArrayInputStream;
48: import java.io.ByteArrayOutputStream;
49: import java.io.IOException;
50: import java.io.OutputStreamWriter;
51: import java.io.PrintWriter;
52: import java.io.Reader;
53: import java.io.StringReader;
54: import java.io.StringWriter;
55: import java.io.UnsupportedEncodingException;
56: import java.io.Writer;
57: import java.lang.reflect.InvocationTargetException;
58: import java.net.InetAddress;
59: import java.net.URLConnection;
60: import java.net.UnknownHostException;
61: import java.nio.charset.Charset;
62: import java.security.AccessController;
63: import java.security.PrivilegedAction;
64: import java.util.Arrays;
65: import java.util.Comparator;
66: import java.util.HashMap;
67: import java.util.Locale;
68: import java.util.Map;
69: import java.util.Properties;
70: import java.util.ResourceBundle;
71: import java.util.logging.ErrorManager;
72: import java.util.logging.Filter;
73: import java.util.logging.Formatter;
74: import java.util.logging.Handler;
75: import java.util.logging.Level;
76: import java.util.logging.LogRecord;
77: import java.util.logging.SimpleFormatter;
78:
79: import static org.eclipse.angus.mail.util.logging.LogManagerProperties.fromLogManager;
80:
81: /**
82: * <code>Handler</code> that formats log records as an email message.
83: *
84: * <p>
85: * This <code>Handler</code> will store a fixed number of log records used to
86: * generate a single email message. When the internal buffer reaches capacity,
87: * all log records are formatted and placed in an email which is sent to an
88: * email server. The code to manually setup this handler can be as simple as
89: * the following:
90: *
91: * <pre>
92: * Properties props = new Properties();
93: * props.put("mail.smtp.host", "my-mail-server");
94: * props.put("mail.to", "me@example.com");
95: * props.put("verify", "local");
96: * MailHandler h = new MailHandler(props);
97: * h.setLevel(Level.WARNING);
98: * </pre>
99: *
100: * <p>
101: * <b><a id="configuration">Configuration:</a></b>
102: * The LogManager should define at least one or more recipient addresses and a
103: * mail host for outgoing email. The code to setup this handler via the
104: * logging properties can be as simple as the following:
105: *
106: * <pre>
107: * #Default MailHandler settings.
108: * org.eclipse.angus.mail.util.logging.MailHandler.mail.smtp.host = my-mail-server
109: * org.eclipse.angus.mail.util.logging.MailHandler.mail.to = me@example.com
110: * org.eclipse.angus.mail.util.logging.MailHandler.level = WARNING
111: * org.eclipse.angus.mail.util.logging.MailHandler.verify = local
112: * </pre>
113: *
114: * For a custom handler, e.g. <code>com.foo.MyHandler</code>, the properties
115: * would be:
116: *
117: * <pre>
118: * #Subclass com.foo.MyHandler settings.
119: * com.foo.MyHandler.mail.smtp.host = my-mail-server
120: * com.foo.MyHandler.mail.to = me@example.com
121: * com.foo.MyHandler.level = WARNING
122: * com.foo.MyHandler.verify = local
123: * </pre>
124: *
125: * All mail properties documented in the <code>Java Mail API</code> cascade to
126: * the LogManager by prefixing a key using the fully qualified class name of
127: * this <code>MailHandler</code> or the fully qualified derived class name dot
128: * mail property. If the prefixed property is not found, then the mail property
129: * itself is searched in the LogManager. By default each
130: * <code>MailHandler</code> is initialized using the following LogManager
131: * configuration properties where <code><handler-name></code> refers to
132: * the fully qualified class name of the handler. If properties are not
133: * defined, or contain invalid values, then the specified default values are
134: * used.
135: *
136: * <ul>
137: * <li><handler-name>.attachment.filters a comma
138: * separated list of <code>Filter</code> class names used to create each
139: * attachment. The literal <code>null</code> is reserved for attachments that
140: * do not require filtering. (defaults to the
141: * {@linkplain java.util.logging.Handler#getFilter() body} filter)
142: *
143: * <li><handler-name>.attachment.formatters a comma
144: * separated list of <code>Formatter</code> class names used to create each
145: * attachment. (default is no attachments)
146: *
147: * <li><handler-name>.attachment.names a comma separated
148: * list of names or <code>Formatter</code> class names of each attachment. All
149: * control characters are removed from the attachment names.
150: * (default is {@linkplain java.util.logging.Formatter#toString() toString}
151: * of the attachment formatter)
152: *
153: * <li><handler-name>.authenticator name of an
154: * {@linkplain jakarta.mail.Authenticator} class used to provide login credentials
155: * to the email server or string literal that is the password used with the
156: * {@linkplain Authenticator#getDefaultUserName() default} user name.
157: * (default is <code>null</code>)
158: *
159: * <li><handler-name>.capacity the max number of
160: * <code>LogRecord</code> objects include in each email message.
161: * (defaults to <code>1000</code>)
162: *
163: * <li><handler-name>.comparator name of a
164: * {@linkplain java.util.Comparator} class used to sort the published
165: * <code>LogRecord</code> objects prior to all formatting.
166: * (defaults to <code>null</code> meaning records are unsorted).
167: *
168: * <li><handler-name>.comparator.reverse a boolean
169: * <code>true</code> to reverse the order of the specified comparator or
170: * <code>false</code> to retain the original order.
171: * (defaults to <code>false</code>)
172: *
173: * <li><handler-name>.enabled a boolean
174: * <code>true</code> to allow this handler to accept records or
175: * <code>false</code> to turn off this handler.
176: * (defaults to <code>true</code>)
177: *
178: * <li><handler-name>.encoding the name of the Java
179: * {@linkplain java.nio.charset.Charset#name() character set} to use for the
180: * email message. (defaults to <code>null</code>, the
181: * {@linkplain jakarta.mail.internet.MimeUtility#getDefaultJavaCharset() default}
182: * platform encoding).
183: *
184: * <li><handler-name>.errorManager name of an
185: * <code>ErrorManager</code> class used to handle any configuration or mail
186: * transport problems. (defaults to <code>java.util.logging.ErrorManager</code>)
187: *
188: * <li><handler-name>.filter name of a <code>Filter</code>
189: * class used for the body of the message. (defaults to <code>null</code>,
190: * allow all records)
191: *
192: * <li><handler-name>.formatter name of a
193: * <code>Formatter</code> class used to format the body of this message.
194: * (defaults to <code>java.util.logging.SimpleFormatter</code>)
195: *
196: * <li><handler-name>.level specifies the default level
197: * for this <code>Handler</code> (defaults to <code>Level.WARNING</code>).
198: *
199: * <li><handler-name>.mail.bcc a comma separated list of
200: * addresses which will be blind carbon copied. Typically, this is set to the
201: * recipients that may need to be privately notified of a log message or
202: * notified that a log message was sent to a third party such as a support team.
203: * The empty string can be used to specify no blind carbon copied address.
204: * (defaults to <code>null</code>, none)
205: *
206: * <li><handler-name>.mail.cc a comma separated list of
207: * addresses which will be carbon copied. Typically, this is set to the
208: * recipients that may need to be notified of a log message but, are not
209: * required to provide direct support. The empty string can be used to specify
210: * no carbon copied address. (defaults to <code>null</code>, none)
211: *
212: * <li><handler-name>.mail.from a comma separated list of
213: * addresses which will be from addresses. Typically, this is set to the email
214: * address identifying the user running the application. The empty string can
215: * be used to override the default behavior and specify no from address.
216: * (defaults to the {@linkplain jakarta.mail.Message#setFrom() local address})
217: *
218: * <li><handler-name>.mail.host the host name or IP
219: * address of the email server. (defaults to <code>null</code>, use
220: * {@linkplain Transport#protocolConnect default}
221: * <code>Java Mail</code> behavior)
222: *
223: * <li><handler-name>.mail.reply.to a comma separated
224: * list of addresses which will be reply-to addresses. Typically, this is set
225: * to the recipients that provide support for the application itself. The empty
226: * string can be used to specify no reply-to address.
227: * (defaults to <code>null</code>, none)
228: *
229: * <li><handler-name>.mail.to a comma separated list of
230: * addresses which will be send-to addresses. Typically, this is set to the
231: * recipients that provide support for the application, system, and/or
232: * supporting infrastructure. The empty string can be used to specify no
233: * send-to address which overrides the default behavior. (defaults to
234: * {@linkplain jakarta.mail.internet.InternetAddress#getLocalAddress
235: * local address}.)
236: *
237: * <li><handler-name>.mail.sender a single address
238: * identifying sender of the email; never equal to the from address. Typically,
239: * this is set to the email address identifying the application itself. The
240: * empty string can be used to specify no sender address.
241: * (defaults to <code>null</code>, none)
242: *
243: * <li><handler-name>.subject the name of a
244: * <code>Formatter</code> class or string literal used to create the subject
245: * line. The empty string can be used to specify no subject. All control
246: * characters are removed from the subject line. (defaults to {@linkplain
247: * CollectorFormatter CollectorFormatter}.)
248: *
249: * <li><handler-name>.pushFilter the name of a
250: * <code>Filter</code> class used to trigger an early push.
251: * (defaults to <code>null</code>, no early push)
252: *
253: * <li><handler-name>.pushLevel the level which will
254: * trigger an early push. (defaults to <code>Level.OFF</code>, only push when
255: * full)
256: *
257: * <li><handler-name>.verify <a id="verify">used</a> to
258: * verify the <code>Handler</code> configuration prior to a push.
259: * <ul>
260: * <li>If the value is not set, equal to an empty string, or equal to the
261: * literal <code>null</code> then no settings are verified prior to a push.
262: * <li>If set to a value of <code>limited</code> then the
263: * <code>Handler</code> will verify minimal local machine settings.
264: * <li>If set to a value of <code>local</code> the <code>Handler</code>
265: * will verify all of settings of the local machine.
266: * <li>If set to a value of <code>resolve</code>, the <code>Handler</code>
267: * will verify all local settings and try to resolve the remote host name
268: * with the domain name server.
269: * <li>If set to a value of <code>login</code>, the <code>Handler</code>
270: * will verify all local settings and try to establish a connection with
271: * the email server.
272: * <li>If set to a value of <code>remote</code>, the <code>Handler</code>
273: * will verify all local settings, try to establish a connection with the
274: * email server, and try to verify the envelope of the email message.
275: * </ul>
276: * If this <code>Handler</code> is only implicitly closed by the
277: * <code>LogManager</code>, then verification should be turned on.
278: * (defaults to <code>null</code>, no verify).
279: * </ul>
280: *
281: * <p>
282: * <b>Normalization:</b>
283: * The error manager, filters, and formatters when loaded from the LogManager
284: * are converted into canonical form inside the MailHandler. The pool of
285: * interned values is limited to each MailHandler object such that no two
286: * MailHandler objects created by the LogManager will be created sharing
287: * identical error managers, filters, or formatters. If a filter or formatter
288: * should <b>not</b> be interned then it is recommended to retain the identity
289: * equals and identity hashCode methods as the implementation. For a filter or
290: * formatter to be interned the class must implement the
291: * {@linkplain java.lang.Object#equals(java.lang.Object) equals}
292: * and {@linkplain java.lang.Object#hashCode() hashCode} methods.
293: * The recommended code to use for stateless filters and formatters is:
294: * <pre>
295: * public boolean equals(Object obj) {
296: * return obj == null ? false : obj.getClass() == getClass();
297: * }
298: *
299: * public int hashCode() {
300: * return 31 * getClass().hashCode();
301: * }
302: * </pre>
303: *
304: * <p>
305: * <b>Sorting:</b>
306: * All <code>LogRecord</code> objects are ordered prior to formatting if this
307: * <code>Handler</code> has a non null comparator. Developers might be
308: * interested in sorting the formatted email by thread id, time, and sequence
309: * properties of a <code>LogRecord</code>. Where as system administrators might
310: * be interested in sorting the formatted email by thrown, level, time, and
311: * sequence properties of a <code>LogRecord</code>. If comparator for this
312: * handler is <code>null</code> then the order is unspecified.
313: *
314: * <p>
315: * <b>Formatting:</b>
316: * The main message body is formatted using the <code>Formatter</code> returned
317: * by <code>getFormatter()</code>. Only records that pass the filter returned
318: * by <code>getFilter()</code> will be included in the message body. The
319: * subject <code>Formatter</code> will see all <code>LogRecord</code> objects
320: * that were published regardless of the current <code>Filter</code>. The MIME
321: * type of the message body can be
322: * {@linkplain FileTypeMap#setDefaultFileTypeMap overridden}
323: * by adding a MIME {@linkplain MimetypesFileTypeMap entry} using the simple
324: * class name of the body formatter as the file extension. The MIME type of the
325: * attachments can be overridden by changing the attachment file name extension
326: * or by editing the default MIME entry for a specific file name extension.
327: *
328: * <p>
329: * <b>Attachments:</b>
330: * This <code>Handler</code> allows multiple attachments per each email message.
331: * The presence of an attachment formatter will change the content type of the
332: * email message to a multi-part message. The attachment order maps directly to
333: * the array index order in this <code>Handler</code> with zero index being the
334: * first attachment. The number of attachment formatters controls the number of
335: * attachments per email and the content type of each attachment. The
336: * attachment filters determine if a <code>LogRecord</code> will be included in
337: * an attachment. If an attachment filter is <code>null</code> then all records
338: * are included for that attachment. Attachments without content will be
339: * omitted from email message. The attachment name formatters create the file
340: * name for an attachment. Custom attachment name formatters can be used to
341: * generate an attachment name based on the contents of the attachment.
342: *
343: * <p>
344: * <b>Push Level and Push Filter:</b>
345: * The push method, push level, and optional push filter can be used to
346: * conditionally trigger a push at or prior to full capacity. When a push
347: * occurs, the current buffer is formatted into an email and is sent to the
348: * email server. If the push method, push level, or push filter trigger a push
349: * then the outgoing email is flagged as high importance with urgent priority.
350: *
351: * <p>
352: * <b>Buffering:</b>
353: * Log records that are published are stored in an internal buffer. When this
354: * buffer reaches capacity the existing records are formatted and sent in an
355: * email. Any published records can be sent before reaching capacity by
356: * explictly calling the <code>flush</code>, <code>push</code>, or
357: * <code>close</code> methods. If a circular buffer is required then this
358: * handler can be wrapped with a {@linkplain java.util.logging.MemoryHandler}
359: * typically with an equivalent capacity, level, and push level.
360: *
361: * <p>
362: * <b>Error Handling:</b>
363: * If the transport of an email message fails, the email is converted to
364: * a {@linkplain jakarta.mail.internet.MimeMessage#writeTo raw}
365: * {@linkplain java.io.ByteArrayOutputStream#toString(java.lang.String) string}
366: * and is then passed as the <code>msg</code> parameter to
367: * {@linkplain Handler#reportError reportError} along with the exception
368: * describing the cause of the failure. This allows custom error managers to
369: * store, {@linkplain jakarta.mail.internet.MimeMessage#MimeMessage(
370: *jakarta.mail.Session, java.io.InputStream) reconstruct}, and resend the
371: * original MimeMessage. The message parameter string is <b>not</b> a raw email
372: * if it starts with value returned from <code>Level.SEVERE.getName()</code>.
373: * Custom error managers can use the following test to determine if the
374: * <code>msg</code> parameter from this handler is a raw email:
375: *
376: * <pre>
377: * public void error(String msg, Exception ex, int code) {
378: * if (msg == null || msg.length() == 0 || msg.startsWith(Level.SEVERE.getName())) {
379: * super.error(msg, ex, code);
380: * } else {
381: * //The 'msg' parameter is a raw email.
382: * }
383: * }
384: * </pre>
385: *
386: * @author Jason Mehrens
387: * @since JavaMail 1.4.3
388: */
389: public class MailHandler extends Handler {
390: /**
391: * Use the emptyFilterArray method.
392: */
393: private static final Filter[] EMPTY_FILTERS = new Filter[0];
394: /**
395: * Use the emptyFormatterArray method.
396: */
397: private static final Formatter[] EMPTY_FORMATTERS = new Formatter[0];
398: /**
399: * Min byte size for header data. Used for initial arrays sizing.
400: */
401: private static final int MIN_HEADER_SIZE = 1024;
402: /**
403: * Cache the off value.
404: */
405: private static final int offValue = Level.OFF.intValue();
406: /**
407: * The action to set the context class loader for use with the Jakarta Mail API.
408: * Load and pin this before it is loaded in the close method. The field is
409: * declared as java.security.PrivilegedAction so
410: * WebappClassLoader.clearReferencesStaticFinal() method will ignore this
411: * field.
412: */
413: private static final PrivilegedAction<Object> MAILHANDLER_LOADER
414: = new GetAndSetContext(MailHandler.class);
415: /**
416: * A thread local mutex used to prevent logging loops. This code has to be
417: * prepared to deal with unexpected null values since the
418: * WebappClassLoader.clearReferencesThreadLocals() and
419: * InnocuousThread.eraseThreadLocals() can remove thread local values.
420: * The MUTEX has 5 states:
421: * 1. A null value meaning default state of not publishing.
422: * 2. MUTEX_PUBLISH on first entry of a push or publish.
423: * 3. The index of the first filter to accept a log record.
424: * 4. MUTEX_REPORT when cycle of records is detected.
425: * 5. MUTEXT_LINKAGE when a linkage error is reported.
426: */
427: private static final ThreadLocal<Integer> MUTEX = new ThreadLocal<>();
428: /**
429: * The marker object used to report a publishing state.
430: * This must be less than the body filter index (-1).
431: */
432: private static final Integer MUTEX_PUBLISH = -2;
433: /**
434: * The used for the error reporting state.
435: * This must be less than the PUBLISH state.
436: */
437: private static final Integer MUTEX_REPORT = -4;
438: /**
439: * The used for linkage error reporting.
440: * This must be less than the REPORT state.
441: */
442: private static final Integer MUTEX_LINKAGE = -8;
443: /**
444: * Used to turn off security checks.
445: */
446: private volatile boolean sealed;
447: /**
448: * Determines if we are inside of a push.
449: * Makes the handler properties read-only during a push.
450: */
451: private boolean isWriting;
452: /**
453: * Holds all of the email server properties.
454: */
455: private Properties mailProps;
456: /**
457: * Holds the authenticator required to login to the email server.
458: */
459: private Authenticator auth;
460: /**
461: * Holds the session object used to generate emails.
462: * Sessions can be shared by multiple threads.
463: * See JDK-6228391 and K 6278.
464: */
465: private Session session;
466: /**
467: * A mapping of log record to matching filter index. Negative one is used
468: * to track the body filter. Zero and greater is used to track the
469: * attachment parts. All indexes less than or equal to the matched value
470: * have already seen the given log record.
471: */
472: private int[] matched;
473: /**
474: * Holds all of the log records that will be used to create the email.
475: */
476: private LogRecord[] data;
477: /**
478: * The number of log records in the buffer.
479: */
480: private int size;
481: /**
482: * The maximum number of log records to format per email.
483: * Used to roughly bound the size of an email.
484: * Every time the capacity is reached, the handler will push.
485: * The capacity will be negative if this handler is closed.
486: * Negative values are used to ensure all records are pushed.
487: */
488: private int capacity;
489: /**
490: * The level recorded at the time the handler was disabled.
491: * Null means enabled and non-null is disabled.
492: */
493: private Level disabledLevel;
494: /**
495: * Used to order all log records prior to formatting. The main email body
496: * and all attachments use the order determined by this comparator. If no
497: * comparator is present the log records will be in no specified order.
498: */
499: private Comparator<? super LogRecord> comparator;
500: /**
501: * Holds the formatter used to create the subject line of the email.
502: * A subject formatter is not required for the email message.
503: * All published records pass through the subject formatter.
504: */
505: private Formatter subjectFormatter;
506: /**
507: * Holds the push level for this handler.
508: * This is only required if an email must be sent prior to shutdown
509: * or before the buffer is full.
510: */
511: private Level pushLevel;
512: /**
513: * Holds the push filter for trigger conditions requiring an early push.
514: * Only gets called if the given log record is greater than or equal
515: * to the push level and the push level is not Level.OFF.
516: */
517: private Filter pushFilter;
518: /**
519: * Holds the entry and body filter for this handler.
520: * There is no way to un-seal the super handler.
521: */
522: private volatile Filter filter;
523: /**
524: * Holds the level for this handler.
525: * There is no way to un-seal the super handler.
526: */
527: private volatile Level logLevel = Level.ALL;
528: /**
529: * Holds the filters for each attachment. Filters are optional for
530: * each attachment. This is declared volatile because this is treated as
531: * copy-on-write. The VO_VOLATILE_REFERENCE_TO_ARRAY warning is a false
532: * positive.
533: */
534: @SuppressWarnings("VolatileArrayField")
535: private volatile Filter[] attachmentFilters;
536: /**
537: * Holds the encoding name for this handler.
538: * There is no way to un-seal the super handler.
539: */
540: private String encoding;
541: /**
542: * Holds the entry and body filter for this handler.
543: * There is no way to un-seal the super handler.
544: */
545: private Formatter formatter;
546: /**
547: * Holds the formatters that create the content for each attachment.
548: * Each formatter maps directly to an attachment. The formatters
549: * getHead, format, and getTail methods are only called if one or more
550: * log records pass through the attachment filters.
551: */
552: private Formatter[] attachmentFormatters;
553: /**
554: * Holds the formatters that create the file name for each attachment.
555: * Each formatter must produce a non null and non empty name.
556: * The final file name will be the concatenation of one getHead call, plus
557: * all of the format calls, plus one getTail call.
558: */
559: private Formatter[] attachmentNames;
560: /**
561: * Used to override the content type for the body and set the content type
562: * for each attachment.
563: */
564: private FileTypeMap contentTypes;
565: /**
566: * Holds the error manager for this handler.
567: * There is no way to un-seal the super handler.
568: */
569: private volatile ErrorManager errorManager = defaultErrorManager();
570:
571: /**
572: * Creates a <code>MailHandler</code> that is configured by the
573: * <code>LogManager</code> configuration properties.
574: *
575: * @throws SecurityException if a security manager exists and the
576: * caller does not have <code>LoggingPermission("control")</code>.
577: */
578: public MailHandler() {
579: init((Properties) null);
580: sealed = true;
581: checkAccess();
582: }
583:
584: /**
585: * Creates a <code>MailHandler</code> that is configured by the
586: * <code>LogManager</code> configuration properties but overrides the
587: * <code>LogManager</code> capacity with the given capacity.
588: *
589: * @param capacity of the internal buffer.
590: * @throws SecurityException if a security manager exists and the
591: * caller does not have <code>LoggingPermission("control")</code>.
592: */
593: public MailHandler(final int capacity) {
594: init((Properties) null);
595: sealed = true;
596: setCapacity0(capacity);
597: }
598:
599: /**
600: * Creates a mail handler with the given mail properties.
601: * The key/value pairs are defined in the <code>Java Mail API</code>
602: * documentation. This <code>Handler</code> will also search the
603: * <code>LogManager</code> for defaults if needed.
604: *
605: * @param props a properties object or null.
606: * @throws SecurityException if a security manager exists and the
607: * caller does not have <code>LoggingPermission("control")</code>.
608: */
609: public MailHandler(Properties props) {
610: init(props);
611: sealed = true;
612: setMailProperties0(props);
613: }
614:
615: /**
616: * Check if this <code>Handler</code> would actually log a given
617: * <code>LogRecord</code> into its internal buffer.
618: * <p>
619: * This method checks if the <code>LogRecord</code> has an appropriate level
620: * and whether it satisfies any <code>Filter</code> including any
621: * attachment filters.
622: * However it does <b>not</b> check whether the <code>LogRecord</code> would
623: * result in a "push" of the buffer contents.
624: *
625: * @param record a <code>LogRecord</code> or null.
626: * @return true if the <code>LogRecord</code> would be logged.
627: */
628: @Override
629: public boolean isLoggable(final LogRecord record) {
630: if (record == null) { //JDK-8233979
631: return false;
632: }
633:
634: int levelValue = getLevel().intValue();
635: if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
636: return false;
637: }
638:
639: Filter body = getFilter();
640: if (body == null || body.isLoggable(record)) {
641: setMatchedPart(-1);
642: return true;
643: }
644:
645: return isAttachmentLoggable(record);
646: }
647:
648: /**
649: * Stores a <code>LogRecord</code> in the internal buffer.
650: * <p>
651: * The <code>isLoggable</code> method is called to check if the given log
652: * record is loggable. If the given record is loggable, it is copied into
653: * an internal buffer. Then the record's level property is compared with
654: * the push level. If the given level of the <code>LogRecord</code>
655: * is greater than or equal to the push level then the push filter is
656: * called. If no push filter exists, the push filter returns true,
657: * or the capacity of the internal buffer has been reached then all buffered
658: * records are formatted into one email and sent to the server.
659: *
660: * @param record description of the log event or null.
661: */
662: @Override
663: public void publish(final LogRecord record) {
664: /**
665: * It is possible for the handler to be closed after the
666: * call to isLoggable. In that case, the current thread
667: * will push to ensure that all published records are sent.
668: * See close().
669: */
670:
671: if (tryMutex()) {
672: try {
673: if (isLoggable(record)) {
674: if (record != null) {
675: record.getSourceMethodName(); //Infer caller.
676: publish0(record);
677: } else { //Override of isLoggable is broken.
678: reportNullError(ErrorManager.WRITE_FAILURE);
679: }
680: }
681: } catch (final LinkageError JDK8152515) {
682: reportLinkageError(JDK8152515, ErrorManager.WRITE_FAILURE);
683: } finally {
684: releaseMutex();
685: }
686: } else {
687: reportUnPublishedError(record);
688: }
689: }
690:
691: /**
692: * Performs the publish after the record has been filtered.
693: *
694: * @param record the record which must not be null.
695: * @since JavaMail 1.4.5
696: */
697: private void publish0(final LogRecord record) {
698: Message msg;
699: boolean priority;
700: synchronized (this) {
701: if (size == data.length && size < capacity) {
702: grow();
703: }
704:
705: if (size < data.length) {
706: //assert data.length == matched.length;
707: matched[size] = getMatchedPart();
708: data[size] = record;
709: ++size; //Be nice to client compiler.
710: priority = isPushable(record);
711: if (priority || size >= capacity) {
712: msg = writeLogRecords(ErrorManager.WRITE_FAILURE);
713: } else {
714: msg = null;
715: }
716: } else {
717: priority = false;
718: msg = null;
719: }
720: }
721:
722: if (msg != null) {
723: send(msg, priority, ErrorManager.WRITE_FAILURE);
724: }
725: }
726:
727: /**
728: * Report to the error manager that a logging loop was detected and
729: * we are going to break the cycle of messages. It is possible that
730: * a custom error manager could continue the cycle in which case
731: * we will stop trying to report errors.
732: *
733: * @param record the record or null.
734: * @since JavaMail 1.4.6
735: */
736: private void reportUnPublishedError(LogRecord record) {
737: final Integer idx = MUTEX.get();
738: if (idx == null || idx > MUTEX_REPORT) {
739: MUTEX.set(MUTEX_REPORT);
740: try {
741: final String msg;
742: if (record != null) {
743: final Formatter f = createSimpleFormatter();
744: msg = "Log record " + record.getSequenceNumber()
745: + " was not published. "
746: + head(f) + format(f, record) + tail(f, "");
747: } else {
748: msg = null;
749: }
750: Exception e = new IllegalStateException(
751: "Recursive publish detected by thread "
752: + Thread.currentThread());
753: reportError(msg, e, ErrorManager.WRITE_FAILURE);
754: } finally {
755: if (idx != null) {
756: MUTEX.set(idx);
757: } else {
758: MUTEX.remove();
759: }
760: }
761: }
762: }
763:
764: /**
765: * Used to detect reentrance by the current thread to the publish method.
766: * This mutex is thread local scope and will not block other threads.
767: * The state is advanced on if the current thread is in a reset state.
768: *
769: * @return true if the mutex was acquired.
770: * @since JavaMail 1.4.6
771: */
772: private boolean tryMutex() {
773: if (MUTEX.get() == null) {
774: MUTEX.set(MUTEX_PUBLISH);
775: return true;
776: } else {
777: return false;
778: }
779: }
780:
781: /**
782: * Releases the mutex held by the current thread.
783: * This mutex is thread local scope and will not block other threads.
784: *
785: * @since JavaMail 1.4.6
786: */
787: private void releaseMutex() {
788: MUTEX.remove();
789: }
790:
791: /**
792: * This is used to get the filter index from when {@code isLoggable} and
793: * {@code isAttachmentLoggable} was invoked by {@code publish} method.
794: *
795: * @return the filter index or MUTEX_PUBLISH if unknown.
796: * @throws NullPointerException if tryMutex was not called.
797: * @since JavaMail 1.5.5
798: */
799: private int getMatchedPart() {
800: //assert Thread.holdsLock(this);
801: Integer idx = MUTEX.get();
802: if (idx == null || idx >= readOnlyAttachmentFilters().length) {
803: idx = MUTEX_PUBLISH;
804: }
805: return idx;
806: }
807:
808: /**
809: * This is used to record the filter index when {@code isLoggable} and
810: * {@code isAttachmentLoggable} was invoked by {@code publish} method.
811: *
812: * @param index the filter index.
813: * @since JavaMail 1.5.5
814: */
815: private void setMatchedPart(int index) {
816: if (MUTEX_PUBLISH.equals(MUTEX.get())) {
817: MUTEX.set(index);
818: }
819: }
820:
821: /**
822: * Clear previous matches when the filters are modified and there are
823: * existing log records that were matched.
824: *
825: * @param index the lowest filter index to clear.
826: * @since JavaMail 1.5.5
827: */
828: private void clearMatches(int index) {
829: assert Thread.holdsLock(this);
830: for (int r = 0; r < size; ++r) {
831: if (matched[r] >= index) {
832: matched[r] = MUTEX_PUBLISH;
833: }
834: }
835: }
836:
837: /**
838: * A callback method for when this object is about to be placed into
839: * commission. This contract is defined by the
840: * {@code org.glassfish.hk2.api.PostConstruct} interface. If this class is
841: * loaded via a lifecycle managed environment other than HK2 then it is
842: * recommended that this method is called either directly or through
843: * extending this class to signal that this object is ready for use.
844: *
845: * @since JavaMail 1.5.3
846: */
847: //@javax.annotation.PostConstruct
848: public void postConstruct() {
849: }
850:
851: /**
852: * A callback method for when this object is about to be decommissioned.
853: * This contract is defined by the {@code org.glassfish.hk2.api.PreDestory}
854: * interface. If this class is loaded via a lifecycle managed environment
855: * other than HK2 then it is recommended that this method is called either
856: * directly or through extending this class to signal that this object will
857: * be destroyed.
858: *
859: * @since JavaMail 1.5.3
860: */
861: //@javax.annotation.PreDestroy
862: public void preDestroy() {
863: /**
864: * Close can require permissions so just trigger a push.
865: */
866: push(false, ErrorManager.CLOSE_FAILURE);
867: }
868:
869: /**
870: * Pushes any buffered records to the email server as high importance with
871: * urgent priority. The internal buffer is then cleared. Does nothing if
872: * called from inside a push.
873: *
874: * @see #flush()
875: */
876: public void push() {
877: push(true, ErrorManager.FLUSH_FAILURE);
878: }
879:
880: /**
881: * Pushes any buffered records to the email server as normal priority.
882: * The internal buffer is then cleared. Does nothing if called from inside
883: * a push.
884: *
885: * @see #push()
886: */
887: @Override
888: public void flush() {
889: push(false, ErrorManager.FLUSH_FAILURE);
890: }
891:
892: /**
893: * Prevents any other records from being published.
894: * Pushes any buffered records to the email server as normal priority.
895: * The internal buffer is then cleared. Once this handler is closed it
896: * will remain closed.
897: * <p>
898: * If this <code>Handler</code> is only implicitly closed by the
899: * <code>LogManager</code>, then <a href="#verify">verification</a> should
900: * be turned on.
901: *
902: * @throws SecurityException if a security manager exists and the
903: * caller does not have <code>LoggingPermission("control")</code>.
904: * @see #flush()
905: */
906: @Override
907: public void close() {
908: try {
909: checkAccess(); //Ensure setLevel works before clearing the buffer.
910: Message msg = null;
911: synchronized (this) {
912: try {
913: msg = writeLogRecords(ErrorManager.CLOSE_FAILURE);
914: } finally { //Change level after formatting.
915: this.logLevel = Level.OFF;
916: this.disabledLevel = null; //free reference
917: /**
918: * The sign bit of the capacity is set to ensure that
919: * records that have passed isLoggable, but have yet to be
920: * added to the internal buffer, are immediately pushed as
921: * an email.
922: */
923: if (this.capacity > 0) {
924: this.capacity = -this.capacity;
925: }
926:
927: //Ensure not inside a push.
928: if (size == 0 && data.length != 1) {
929: this.data = new LogRecord[1];
930: this.matched = new int[this.data.length];
931: }
932: }
933: }
934:
935: if (msg != null) {
936: send(msg, false, ErrorManager.CLOSE_FAILURE);
937: }
938: } catch (final LinkageError JDK8152515) {
939: reportLinkageError(JDK8152515, ErrorManager.CLOSE_FAILURE);
940: }
941: }
942:
943: /**
944: * Gets the enabled status of this handler.
945: *
946: * @return true if this handler is accepting log records.
947: * @see #setEnabled(boolean)
948: * @see #setLevel(java.util.logging.Level)
949: * @since Angus Mail 2.0.3
950: */
951: public boolean isEnabled() {
952: return this.logLevel.intValue() != offValue; //Volatile read
953: }
954:
955: /**
956: * Used to enable or disable this handler.
957: *
958: * Pushes any buffered records to the email server as normal priority.
959: * The internal buffer is then cleared.
960: *
961: * @param enabled true to enable and false to disable.
962: * @throws SecurityException if a security manager exists and if the caller
963: * does not have <code>LoggingPermission("control")</code>.
964: * @see #flush()
965: * @see #isEnabled()
966: * @since Angus Mail 2.0.3
967: */
968: public void setEnabled(final boolean enabled) {
969: checkAccess();
970: setEnabled0(enabled);
971: }
972:
973: /**
974: * Used to enable or disable this handler.
975: *
976: * Pushes any buffered records to the email server as normal priority.
977: * The internal buffer is then cleared.
978: *
979: * @param enabled true to enable and false to disable.
980: * @since Angus Mail 2.0.3
981: */
982: private synchronized void setEnabled0(final boolean enabled) {
983: if (this.capacity > 0) { //handler is open
984: this.push(false, ErrorManager.FLUSH_FAILURE);
985: if (enabled) {
986: if (disabledLevel != null) { //was disabled
987: this.logLevel = this.disabledLevel;
988: this.disabledLevel = null;
989: }
990: } else {
991: if (disabledLevel == null) {
992: this.disabledLevel = this.logLevel;
993: this.logLevel = Level.OFF;
994: }
995: }
996: }
997: }
998:
999: /**
1000: * Set the log level specifying which message levels will be
1001: * logged by this <code>Handler</code>. Message levels lower than this
1002: * value will be discarded.
1003: *
1004: * @param newLevel the new value for the log level
1005: * @throws NullPointerException if <code>newLevel</code> is
1006: * <code>null</code>.
1007: * @throws SecurityException if a security manager exists and
1008: * the caller does not have
1009: * <code>LoggingPermission("control")</code>.
1010: */
1011: @Override
1012: public void setLevel(final Level newLevel) {
1013: if (newLevel == null) {
1014: throw new NullPointerException();
1015: }
1016: checkAccess();
1017:
1018: //Don't allow a closed handler to be opened (half way).
1019: synchronized (this) { //Wait for writeLogRecords.
1020: if (this.capacity > 0) {
1021: //if disabled then track the new level to be used when enabled.
1022: if (this.disabledLevel != null) {
1023: this.disabledLevel = newLevel;
1024: } else {
1025: this.logLevel = newLevel;
1026: }
1027: }
1028: }
1029: }
1030:
1031: /**
1032: * Get the log level specifying which messages will be logged by this
1033: * <code>Handler</code>. Message levels lower than this level will be
1034: * discarded.
1035: *
1036: * @return the level of messages being logged.
1037: */
1038: @Override
1039: public Level getLevel() {
1040: return logLevel; //Volatile access.
1041: }
1042:
1043: /**
1044: * Retrieves the ErrorManager for this Handler.
1045: *
1046: * @return the ErrorManager for this Handler
1047: * @throws SecurityException if a security manager exists and if the caller
1048: * does not have <code>LoggingPermission("control")</code>.
1049: */
1050: @Override
1051: public ErrorManager getErrorManager() {
1052: checkAccess();
1053: return this.errorManager; //Volatile access.
1054: }
1055:
1056: /**
1057: * Define an ErrorManager for this Handler.
1058: * <p>
1059: * The ErrorManager's "error" method will be invoked if any errors occur
1060: * while using this Handler.
1061: *
1062: * @param em the new ErrorManager
1063: * @throws SecurityException if a security manager exists and if the
1064: * caller does not have <code>LoggingPermission("control")</code>.
1065: * @throws NullPointerException if the given error manager is null.
1066: */
1067: @Override
1068: public void setErrorManager(final ErrorManager em) {
1069: checkAccess();
1070: setErrorManager0(em);
1071: }
1072:
1073: /**
1074: * Sets the error manager on this handler and the super handler. In secure
1075: * environments the super call may not be allowed which is not a failure
1076: * condition as it is an attempt to free the unused handler error manager.
1077: *
1078: * @param em a non null error manager.
1079: * @throws NullPointerException if the given error manager is null.
1080: * @since JavaMail 1.5.6
1081: */
1082: private void setErrorManager0(final ErrorManager em) {
1083: if (em == null) {
1084: throw new NullPointerException();
1085: }
1086: try {
1087: synchronized (this) { //Wait for writeLogRecords.
1088: this.errorManager = em;
1089: super.setErrorManager(em); //Try to free super error manager.
1090: }
1091: } catch (RuntimeException | LinkageError ignore) {
1092: }
1093: }
1094:
1095: /**
1096: * Get the current <code>Filter</code> for this <code>Handler</code>.
1097: *
1098: * @return a <code>Filter</code> object (may be null)
1099: */
1100: @Override
1101: public Filter getFilter() {
1102: return this.filter; //Volatile access.
1103: }
1104:
1105: /**
1106: * Set a <code>Filter</code> to control output on this <code>Handler</code>.
1107: * <P>
1108: * For each call of <code>publish</code> the <code>Handler</code> will call
1109: * this <code>Filter</code> (if it is non-null) to check if the
1110: * <code>LogRecord</code> should be published or discarded.
1111: *
1112: * @param newFilter a <code>Filter</code> object (may be null)
1113: * @throws SecurityException if a security manager exists and if the caller
1114: * does not have <code>LoggingPermission("control")</code>.
1115: */
1116: @Override
1117: public void setFilter(final Filter newFilter) {
1118: checkAccess();
1119: synchronized (this) { //Wait for writeLogRecords.
1120: if (newFilter != filter) {
1121: clearMatches(-1);
1122: }
1123: this.filter = newFilter; //Volatile access.
1124: }
1125: }
1126:
1127: /**
1128: * Return the character encoding for this <code>Handler</code>.
1129: *
1130: * @return The encoding name. May be null, which indicates the default
1131: * encoding should be used.
1132: */
1133: @Override
1134: public synchronized String getEncoding() {
1135: return this.encoding;
1136: }
1137:
1138: /**
1139: * Set the character encoding used by this <code>Handler</code>.
1140: * <p>
1141: * The encoding should be set before any <code>LogRecords</code> are written
1142: * to the <code>Handler</code>.
1143: *
1144: * @param encoding The name of a supported character encoding. May be
1145: * null, to indicate the default platform encoding.
1146: * @throws SecurityException if a security manager exists and if the caller
1147: * does not have <code>LoggingPermission("control")</code>.
1148: * @throws UnsupportedEncodingException if the named encoding is not
1149: * supported.
1150: */
1151: @Override
1152: public void setEncoding(String encoding) throws UnsupportedEncodingException {
1153: checkAccess();
1154: setEncoding0(encoding);
1155: }
1156:
1157: /**
1158: * Set the character encoding used by this handler. This method does not
1159: * check permissions of the caller.
1160: *
1161: * @param e any encoding name or null for the default.
1162: * @throws UnsupportedEncodingException if the given encoding is not supported.
1163: */
1164: private void setEncoding0(String e) throws UnsupportedEncodingException {
1165: if (e != null) {
1166: try {
1167: if (!java.nio.charset.Charset.isSupported(e)) {
1168: throw new UnsupportedEncodingException(e);
1169: }
1170: } catch (java.nio.charset.IllegalCharsetNameException icne) {
1171: throw new UnsupportedEncodingException(e);
1172: }
1173: }
1174:
1175: synchronized (this) { //Wait for writeLogRecords.
1176: this.encoding = e;
1177: }
1178: }
1179:
1180: /**
1181: * Return the <code>Formatter</code> for this <code>Handler</code>.
1182: *
1183: * @return the <code>Formatter</code> (may be null).
1184: */
1185: @Override
1186: public synchronized Formatter getFormatter() {
1187: return this.formatter;
1188: }
1189:
1190: /**
1191: * Set a <code>Formatter</code>. This <code>Formatter</code> will be used
1192: * to format <code>LogRecords</code> for this <code>Handler</code>.
1193: * <p>
1194: * Some <code>Handlers</code> may not use <code>Formatters</code>, in which
1195: * case the <code>Formatter</code> will be remembered, but not used.
1196: *
1197: * @param newFormatter the <code>Formatter</code> to use (may not be null)
1198: * @throws SecurityException if a security manager exists and if the caller
1199: * does not have <code>LoggingPermission("control")</code>.
1200: * @throws NullPointerException if the given formatter is null.
1201: */
1202: @Override
1203: public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
1204: checkAccess();
1205: if (newFormatter == null) {
1206: throw new NullPointerException();
1207: }
1208: this.formatter = newFormatter;
1209: }
1210:
1211: /**
1212: * Gets the push level. The default is <code>Level.OFF</code> meaning that
1213: * this <code>Handler</code> will only push when the internal buffer is full.
1214: *
1215: * @return a non-null push level.
1216: */
1217: public final synchronized Level getPushLevel() {
1218: return this.pushLevel;
1219: }
1220:
1221: /**
1222: * Sets the push level. This level is used to trigger a push so that
1223: * all pending records are formatted and sent to the email server. When
1224: * the push level triggers a send, the resulting email is flagged as
1225: * high importance with urgent priority.
1226: *
1227: * @param level any level object or null meaning off.
1228: * @throws SecurityException if a security manager exists and the
1229: * caller does not have <code>LoggingPermission("control")</code>.
1230: * @throws IllegalStateException if called from inside a push.
1231: */
1232: public final synchronized void setPushLevel(Level level) {
1233: checkAccess();
1234: if (level == null) {
1235: level = Level.OFF;
1236: }
1237:
1238: if (isWriting) {
1239: throw new IllegalStateException();
1240: }
1241: this.pushLevel = level;
1242: }
1243:
1244: /**
1245: * Gets the push filter. The default is <code>null</code>.
1246: *
1247: * @return the push filter or <code>null</code>.
1248: */
1249: public final synchronized Filter getPushFilter() {
1250: return this.pushFilter;
1251: }
1252:
1253: /**
1254: * Sets the push filter. This filter is only called if the given
1255: * <code>LogRecord</code> level was greater than the push level. If this
1256: * filter returns <code>true</code>, all pending records are formatted and
1257: * sent to the email server. When the push filter triggers a send, the
1258: * resulting email is flagged as high importance with urgent priority.
1259: *
1260: * @param filter push filter or <code>null</code>
1261: * @throws SecurityException if a security manager exists and the
1262: * caller does not have <code>LoggingPermission("control")</code>.
1263: * @throws IllegalStateException if called from inside a push.
1264: */
1265: public final synchronized void setPushFilter(final Filter filter) {
1266: checkAccess();
1267: if (isWriting) {
1268: throw new IllegalStateException();
1269: }
1270: this.pushFilter = filter;
1271: }
1272:
1273: /**
1274: * Gets the comparator used to order all <code>LogRecord</code> objects
1275: * prior to formatting. If <code>null</code> then the order is unspecified.
1276: *
1277: * @return the <code>LogRecord</code> comparator.
1278: */
1279: public final synchronized Comparator<? super LogRecord> getComparator() {
1280: return this.comparator;
1281: }
1282:
1283: /**
1284: * Sets the comparator used to order all <code>LogRecord</code> objects
1285: * prior to formatting. If <code>null</code> then the order is unspecified.
1286: *
1287: * @param c the <code>LogRecord</code> comparator.
1288: * @throws SecurityException if a security manager exists and the
1289: * caller does not have <code>LoggingPermission("control")</code>.
1290: * @throws IllegalStateException if called from inside a push.
1291: */
1292: public final synchronized void setComparator(Comparator<? super LogRecord> c) {
1293: checkAccess();
1294: if (isWriting) {
1295: throw new IllegalStateException();
1296: }
1297: this.comparator = c;
1298: }
1299:
1300: /**
1301: * Gets the number of log records the internal buffer can hold. When
1302: * capacity is reached, <code>Handler</code> will format all
1303: * <code>LogRecord</code> objects into one email message.
1304: *
1305: * @return the capacity.
1306: */
1307: public final synchronized int getCapacity() {
1308: assert capacity != Integer.MIN_VALUE && capacity != 0 : capacity;
1309: return Math.abs(capacity);
1310: }
1311:
1312: /**
1313: * Sets the capacity for this handler.
1314: *
1315: * Pushes any buffered records to the email server as normal priority.
1316: * The internal buffer is then cleared.
1317: *
1318: * @param newCapacity the max number of records.
1319: * @throws SecurityException if a security manager exists and the caller
1320: * does not have <code>LoggingPermission("control")</code>.
1321: * @throws IllegalArgumentException is the new capacity is less than one.
1322: * @throws IllegalStateException if called from inside a push.
1323: * @see #flush()
1324: * @since Angus Mail 2.0.3
1325: */
1326: public final synchronized void setCapacity(int newCapacity) {
1327: setCapacity0(newCapacity);
1328: }
1329:
1330: /**
1331: * Gets the <code>Authenticator</code> used to login to the email server.
1332: *
1333: * @return an <code>Authenticator</code> or <code>null</code> if none is
1334: * required.
1335: * @throws SecurityException if a security manager exists and the
1336: * caller does not have <code>LoggingPermission("control")</code>.
1337: */
1338: public final synchronized Authenticator getAuthenticator() {
1339: checkAccess();
1340: return this.auth;
1341: }
1342:
1343: /**
1344: * Sets the <code>Authenticator</code> used to login to the email server.
1345: *
1346: * @param auth an <code>Authenticator</code> object or null if none is
1347: * required.
1348: * @throws SecurityException if a security manager exists and the
1349: * caller does not have <code>LoggingPermission("control")</code>.
1350: * @throws IllegalStateException if called from inside a push.
1351: */
1352: public final void setAuthenticator(final Authenticator auth) {
1353: this.setAuthenticator0(auth);
1354: }
1355:
1356: /**
1357: * Sets the <code>Authenticator</code> used to login to the email server.
1358: *
1359: * @param password a password, empty array can be used to only supply a
1360: * user name set by <code>mail.user</code> property, or null if no
1361: * credentials are required.
1362: * @throws SecurityException if a security manager exists and the
1363: * caller does not have <code>LoggingPermission("control")</code>.
1364: * @throws IllegalStateException if called from inside a push.
1365: * @see String#toCharArray()
1366: * @since JavaMail 1.4.6
1367: */
1368: public final void setAuthenticator(final char... password) {
1369: if (password == null) {
1370: setAuthenticator0((Authenticator) null);
1371: } else {
1372: setAuthenticator0(DefaultAuthenticator.of(new String(password)));
1373: }
1374: }
1375:
1376: /**
1377: * Sets the <code>Authenticator</code> class name or password used to login
1378: * to the email server.
1379: *
1380: * @param auth the class name of the authenticator, literal password, or
1381: * empty string can be used to only supply a user name set by
1382: * <code>mail.user</code> property. A null value can be used if no
1383: * credentials are required.
1384: * @throws SecurityException if a security manager exists and the caller
1385: * does not have <code>LoggingPermission("control")</code>.
1386: * @throws IllegalStateException if called from inside a push.
1387: * @see #getAuthenticator()
1388: * @see #setAuthenticator(char...)
1389: * @since Angus Mail 2.0.3
1390: */
1391: public final synchronized void setAuthentication(final String auth) {
1392: setAuthenticator0(newAuthenticator(auth));
1393: }
1394:
1395: /**
1396: * A private hook to handle possible future overrides. See public method.
1397: *
1398: * @param auth see public method.
1399: * @throws SecurityException if a security manager exists and the
1400: * caller does not have <code>LoggingPermission("control")</code>.
1401: * @throws IllegalStateException if called from inside a push.
1402: */
1403: private void setAuthenticator0(final Authenticator auth) {
1404: checkAccess();
1405:
1406: Session settings;
1407: synchronized (this) {
1408: if (isWriting) {
1409: throw new IllegalStateException();
1410: }
1411: this.auth = auth;
1412: settings = updateSession();
1413: }
1414: verifySettings(settings);
1415: }
1416:
1417: /**
1418: * Sets the mail properties used for the session. The key/value pairs
1419: * are defined in the <code>Java Mail API</code> documentation. This
1420: * <code>Handler</code> will also search the <code>LogManager</code> for
1421: * defaults if needed.
1422: *
1423: * @param props properties object or null.
1424: * @throws SecurityException if a security manager exists and the
1425: * caller does not have <code>LoggingPermission("control")</code>.
1426: * @throws IllegalStateException if called from inside a push.
1427: */
1428: public final void setMailProperties(Properties props) {
1429: this.setMailProperties0(props);
1430: }
1431:
1432: /**
1433: * A private hook to handle overrides when the public method is declared
1434: * non final. See public method for details.
1435: *
1436: * @param props properties object or null.
1437: */
1438: private void setMailProperties0(Properties props) {
1439: checkAccess();
1440: if (props != null) {
1441: props = (Properties) props.clone(); //Allow subclass.
1442: } else {
1443: props = new Properties();
1444: }
1445:
1446: Session settings;
1447: synchronized (this) {
1448: if (isWriting) {
1449: throw new IllegalStateException();
1450: }
1451: this.mailProps = props;
1452: settings = updateSession();
1453: }
1454: verifySettings(settings);
1455: }
1456:
1457: /**
1458: * Gets a copy of the mail properties used for the session.
1459: *
1460: * @return a non null properties object.
1461: * @throws SecurityException if a security manager exists and the
1462: * caller does not have <code>LoggingPermission("control")</code>.
1463: */
1464: public final Properties getMailProperties() {
1465: checkAccess();
1466: final Properties props;
1467: synchronized (this) {
1468: props = this.mailProps;
1469: }
1470: return (Properties) props.clone();
1471: }
1472:
1473: /**
1474: * Parses the given properties lines then clears and sets all of the mail
1475: * properties used for the session. Any parsing errors are reported to the
1476: * error manager. This method provides bean style properties support.
1477: * <p>
1478: * The given string should be treated as lines of a properties file. The
1479: * character {@code '='} or {@code ':'} are used to separate an entry also
1480: * known as a key/value pair. The line terminator characters {@code \r} or
1481: * {@code \n} or {@code \r\n} are used to separate each entry. The
1482: * characters {@code '#!'} together can be used to signal the end of an
1483: * entry when escape characters are not supported.
1484: * <p>
1485: * The example from the <a href="#configuration">configuration</a>
1486: * section would be formatted as the following:
1487: * <pre>
1488: * "mail.smtp.host:my-mail-server#!mail.to:me@example.com#!verify:local"
1489: * </pre>
1490: * <p>
1491: * The key/value pairs are defined in the <code>Java Mail API</code>
1492: * documentation. This <code>Handler</code> will also search the
1493: * <code>LogManager</code> for defaults if needed.
1494: *
1495: * @param entries one or more key/value pairs. An empty string, null value
1496: * or, the literal null are all treated as empty properties and will simply
1497: * clear all existing mail properties assigned to this handler.
1498: * @throws SecurityException if a security manager exists and the caller
1499: * does not have <code>LoggingPermission("control")</code>.
1500: * @throws IllegalStateException if called from inside a push.
1501: * @see #getMailProperties()
1502: * @see java.io.StringReader
1503: * @see java.util.Properties#load(Reader)
1504: * @since Angus Mail 2.0.3
1505: */
1506: public final void setMailEntries(String entries) {
1507: final Properties props = new Properties();
1508: if (hasValue(entries)) {
1509: /**
1510: * The characters # and ! are used for comment lines in properties
1511: * format. The characters \r or \n are not allowed in WildFly form
1512: * validation however, properties comment characters are allowed.
1513: * Comment lines are useless for this handler therefore, "#!"
1514: * characters are used to represent logical lines and are assumed to
1515: * not be present together in a key or value.
1516: */
1517: try {
1518: entries = entries.replace("#!", "\r\n");
1519: //Dynamic cast used so byte code verifier doesn't load StringReader
1520: props.load(Reader.class.cast(new StringReader(entries)));
1521: } catch (IOException | RuntimeException ex) {
1522: reportError(entries, ex, ErrorManager.OPEN_FAILURE);
1523: //Allow a partial load of properties to be set
1524: }
1525: }
1526: setMailProperties0(props);
1527: }
1528:
1529: /**
1530: * Formats the current mail properties as properties lines. Any formatting
1531: * errors are reported to the error manager. The returned string should be
1532: * treated as lines of a properties file. The value of this string is
1533: * reconstructed from the properties object and therefore may be different
1534: * from what was originally set. This method provides bean style properties
1535: * support.
1536: *
1537: * @return string representation of the mail properties.
1538: * @throws SecurityException if a security manager exists and the caller
1539: * does not have <code>LoggingPermission("control")</code>.
1540: * @throws IllegalStateException if called from inside a push.
1541: * @see #getMailProperties()
1542: * @see java.io.StringWriter
1543: * @see java.util.Properties#store(java.io.Writer, java.lang.String)
1544: * @since Angus Mail 2.0.3
1545: */
1546: public final String getMailEntries() {
1547: checkAccess();
1548: final Properties props;
1549: synchronized (this) {
1550: props = this.mailProps;
1551: }
1552:
1553: final StringWriter sw = new StringWriter();
1554: try {
1555: //Dynamic cast used so byte code verifier doesn't load StringWriter
1556: props.store(Writer.class.cast(sw), (String) null);
1557: } catch (IOException | RuntimeException ex) {
1558: reportError(props.toString(), ex, ErrorManager.GENERIC_FAILURE);
1559: //partially constructed values are allowed to be returned
1560: }
1561: return sw.toString();
1562: }
1563:
1564: /**
1565: * Gets the attachment filters. If the attachment filter does not
1566: * allow any <code>LogRecord</code> to be formatted, the attachment may
1567: * be omitted from the email.
1568: *
1569: * @return a non null array of attachment filters.
1570: */
1571: public final Filter[] getAttachmentFilters() {
1572: return readOnlyAttachmentFilters().clone();
1573: }
1574:
1575: /**
1576: * Sets the attachment filters.
1577: *
1578: * @param filters array of filters. A <code>null</code> array is treated
1579: * the same as an empty array and will remove all attachments. A
1580: * <code>null</code> index value means that all records are allowed for the
1581: * attachment at that index.
1582: * @throws SecurityException if a security manager exists and the
1583: * caller does not have <code>LoggingPermission("control")</code>.
1584: * @throws IllegalStateException if called from inside a push.
1585: */
1586: public final void setAttachmentFilters(Filter... filters) {
1587: checkAccess();
1588: if (filters == null || filters.length == 0) {
1589: filters = emptyFilterArray();
1590: } else {
1591: filters = Arrays.copyOf(filters, filters.length, Filter[].class);
1592: }
1593:
1594: synchronized (this) {
1595: if (isWriting) {
1596: throw new IllegalStateException();
1597: }
1598:
1599: if (size != 0) {
1600: final int len = Math.min(filters.length, attachmentFilters.length);
1601: int i = 0;
1602: for (; i < len; ++i) {
1603: if (filters[i] != attachmentFilters[i]) {
1604: break;
1605: }
1606: }
1607: clearMatches(i);
1608: }
1609: this.attachmentFilters = filters;
1610: this.alignAttachmentFormatters(filters.length);
1611: this.alignAttachmentNames(filters.length);
1612: }
1613: }
1614:
1615: /**
1616: * Gets the attachment formatters. This <code>Handler</code> is using
1617: * attachments only if the returned array length is non zero.
1618: *
1619: * @return a non <code>null</code> array of formatters.
1620: */
1621: public final Formatter[] getAttachmentFormatters() {
1622: Formatter[] formatters;
1623: synchronized (this) {
1624: formatters = this.attachmentFormatters;
1625: }
1626: return formatters.clone();
1627: }
1628:
1629: /**
1630: * Sets the attachment <code>Formatter</code> object for this handler.
1631: * The number of formatters determines the number of attachments per
1632: * email. This method should be the first attachment method called.
1633: * To remove all attachments, call this method with empty array.
1634: *
1635: * @param formatters an array of formatters. A null array is treated as an
1636: * empty array. Any null indexes is replaced with a
1637: * {@linkplain java.util.logging.SimpleFormatter SimpleFormatter}.
1638: * @throws SecurityException if a security manager exists and the
1639: * caller does not have <code>LoggingPermission("control")</code>.
1640: * @throws IllegalStateException if called from inside a push.
1641: */
1642: public final void setAttachmentFormatters(Formatter... formatters) {
1643: checkAccess();
1644: if (formatters == null || formatters.length == 0) { //Null check and length check.
1645: formatters = emptyFormatterArray();
1646: } else {
1647: formatters = Arrays.copyOf(formatters,
1648: formatters.length, Formatter[].class);
1649: for (int i = 0; i < formatters.length; ++i) {
1650: if (formatters[i] == null) {
1651: formatters[i] = createSimpleFormatter();
1652: }
1653: }
1654: }
1655:
1656: synchronized (this) {
1657: if (isWriting) {
1658: throw new IllegalStateException();
1659: }
1660:
1661: this.attachmentFormatters = formatters;
1662: this.alignAttachmentFilters(formatters.length);
1663: this.alignAttachmentNames(formatters.length);
1664: }
1665: }
1666:
1667: /**
1668: * Gets the attachment name formatters.
1669: * If the attachment names were set using explicit names then
1670: * the names can be returned by calling <code>toString</code> on each
1671: * attachment name formatter.
1672: *
1673: * @return non <code>null</code> array of attachment name formatters.
1674: */
1675: public final Formatter[] getAttachmentNames() {
1676: final Formatter[] formatters;
1677: synchronized (this) {
1678: formatters = this.attachmentNames;
1679: }
1680: return formatters.clone();
1681: }
1682:
1683: /**
1684: * Sets the attachment file name for each attachment. All control
1685: * characters are removed from the attachment names.
1686: * This method will create a set of custom formatters.
1687: *
1688: * @param names an array of names. A null array is treated as an empty
1689: * array. Any null or empty indexes are replaced with the string
1690: * representation of the attachment formatter.
1691: * @throws SecurityException if a security manager exists and the
1692: * caller does not have <code>LoggingPermission("control")</code>.
1693: * @throws IllegalStateException if called from inside a push.
1694: * @see Character#isISOControl(char)
1695: * @see Character#isISOControl(int)
1696: */
1697:
1698: public final void setAttachmentNames(final String... names) {
1699: checkAccess();
1700:
1701: final Formatter[] formatters;
1702: if (names == null || names.length == 0) {
1703: formatters = emptyFormatterArray();
1704: } else {
1705: formatters = new Formatter[names.length];
1706: }
1707:
1708: synchronized (this) {
1709: if (isWriting) {
1710: throw new IllegalStateException();
1711: }
1712:
1713: this.alignAttachmentFormatters(formatters.length);
1714: this.alignAttachmentFilters(formatters.length);
1715: for (int i = 0; i < formatters.length; ++i) {
1716: //names is non-null if formatters length is not zero
1717: String name = names[i];
1718: if (isEmpty(name)) {
1719: name = toString(this.attachmentFormatters[i]);
1720: }
1721: formatters[i] = TailNameFormatter.of(name);
1722: }
1723: this.attachmentNames = formatters;
1724: }
1725: }
1726:
1727: /**
1728: * Sets the attachment file name formatters. The format method of each
1729: * attachment formatter will see only the <code>LogRecord</code> objects
1730: * that passed its attachment filter during formatting. The format method
1731: * will typically return an empty string. Instead of being used to format
1732: * records, it is used to gather information about the contents of an
1733: * attachment. The <code>getTail</code> method should be used to construct
1734: * the attachment file name and reset any formatter collected state. All
1735: * control characters will be removed from the output of the formatter. The
1736: * <code>toString</code> method of the given formatter should be overridden
1737: * to provide a useful attachment file name, if possible.
1738: *
1739: * @param formatters and array of attachment name formatters.
1740: * @throws SecurityException if a security manager exists and the
1741: * caller does not have <code>LoggingPermission("control")</code>.
1742: * @throws IllegalStateException if called from inside a push.
1743: * @see Character#isISOControl(char)
1744: * @see Character#isISOControl(int)
1745: */
1746: public final void setAttachmentNames(Formatter... formatters) {
1747: setAttachmentNameFormatters(formatters);
1748: }
1749:
1750: /**
1751: * Sets the attachment file name formatters. The format method of each
1752: * attachment formatter will see only the <code>LogRecord</code> objects
1753: * that passed its attachment filter during formatting. The format method
1754: * will typically return an empty string. Instead of being used to format
1755: * records, it is used to gather information about the contents of an
1756: * attachment. The <code>getTail</code> method should be used to construct
1757: * the attachment file name and reset any formatter collected state. All
1758: * control characters will be removed from the output of the formatter. The
1759: * <code>toString</code> method of the given formatter should be overridden
1760: * to provide a useful attachment file name, if possible.
1761: *
1762: * @param formatters and array of attachment name formatters.
1763: * @throws SecurityException if a security manager exists and the
1764: * caller does not have <code>LoggingPermission("control")</code>.
1765: * @throws IllegalStateException if called from inside a push.
1766: * @see Character#isISOControl(char)
1767: * @see Character#isISOControl(int)
1768: * @since Angus Mail 2.0.3
1769: */
1770: public final void setAttachmentNameFormatters(Formatter... formatters) {
1771: checkAccess();
1772:
1773: if (formatters == null || formatters.length == 0) {
1774: formatters = emptyFormatterArray();
1775: } else {
1776: formatters = Arrays.copyOf(formatters, formatters.length,
1777: Formatter[].class);
1778: }
1779:
1780: synchronized (this) {
1781: if (isWriting) {
1782: throw new IllegalStateException();
1783: }
1784:
1785: this.alignAttachmentFormatters(formatters.length);
1786: this.alignAttachmentFilters(formatters.length);
1787: for (int i = 0; i < formatters.length; ++i) {
1788: if (formatters[i] == null) {
1789: formatters[i] = TailNameFormatter.of(toString(this.attachmentFormatters[i]));
1790: }
1791: }
1792: this.attachmentNames = formatters;
1793: }
1794: }
1795:
1796: /**
1797: * Gets the formatter used to create the subject line.
1798: * If the subject was created using a literal string then
1799: * the <code>toString</code> method can be used to get the subject line.
1800: *
1801: * @return the formatter.
1802: */
1803: public final synchronized Formatter getSubject() {
1804: return getSubjectFormatter();
1805: }
1806:
1807: /**
1808: * Gets the formatter used to create the subject line.
1809: * If the subject was created using a literal string then
1810: * the <code>toString</code> method can be used to get the subject line.
1811: *
1812: * @return the formatter.
1813: * @since Angus Mail 2.0.3
1814: */
1815: public final synchronized Formatter getSubjectFormatter() {
1816: return this.subjectFormatter;
1817: }
1818:
1819: /**
1820: * Sets a literal string for the email subject. All control characters are
1821: * removed from the subject line of the email
1822: *
1823: * @param subject a non <code>null</code> string.
1824: * @throws SecurityException if a security manager exists and the
1825: * caller does not have <code>LoggingPermission("control")</code>.
1826: * @throws NullPointerException if <code>subject</code> is
1827: * <code>null</code>.
1828: * @throws IllegalStateException if called from inside a push.
1829: * @see Character#isISOControl(char)
1830: * @see Character#isISOControl(int)
1831: */
1832: public synchronized final void setSubject(final String subject) {
1833: if (subject != null) {
1834: this.setSubjectFormatter(TailNameFormatter.of(subject));
1835: } else {
1836: checkAccess();
1837: initSubject((String) null);
1838: }
1839: }
1840:
1841: /**
1842: * Sets the subject formatter for email. The format method of the subject
1843: * formatter will see all <code>LogRecord</code> objects that were published
1844: * to this <code>Handler</code> during formatting and will typically return
1845: * an empty string. This formatter is used to gather information to create
1846: * a summary about what information is contained in the email. The
1847: * <code>getTail</code> method should be used to construct the subject and
1848: * reset any formatter collected state. All control characters
1849: * will be removed from the formatter output. The <code>toString</code>
1850: * method of the given formatter should be overridden to provide a useful
1851: * subject, if possible.
1852: *
1853: * @param format the subject formatter or null for default formatter.
1854: * @throws SecurityException if a security manager exists and the
1855: * caller does not have <code>LoggingPermission("control")</code>.
1856: * @throws IllegalStateException if called from inside a push.
1857: * @see Character#isISOControl(char)
1858: * @see Character#isISOControl(int)
1859: */
1860: public final void setSubject(final Formatter format) {
1861: setSubjectFormatter(format);
1862: }
1863:
1864: /**
1865: * Sets the subject formatter for email. The format method of the subject
1866: * formatter will see all <code>LogRecord</code> objects that were published
1867: * to this <code>Handler</code> during formatting and will typically return
1868: * an empty string. This formatter is used to gather information to create
1869: * a summary about what information is contained in the email. The
1870: * <code>getTail</code> method should be used to construct the subject and
1871: * reset any formatter collected state. All control characters
1872: * will be removed from the formatter output. The <code>toString</code>
1873: * method of the given formatter should be overridden to provide a useful
1874: * subject, if possible.
1875: *
1876: * @param format the subject formatter or null for default formatter.
1877: * @throws SecurityException if a security manager exists and the
1878: * caller does not have <code>LoggingPermission("control")</code>.
1879: * @throws IllegalStateException if called from inside a push.
1880: * @see Character#isISOControl(char)
1881: * @see Character#isISOControl(int)
1882: * @since Angus Mail 2.0.3
1883: */
1884: public synchronized final void setSubjectFormatter(final Formatter format) {
1885: checkAccess();
1886: if (format != null) {
1887: if (isWriting) {
1888: throw new IllegalStateException();
1889: }
1890: this.subjectFormatter = format;
1891: } else {
1892: initSubject((String) null);
1893: }
1894: }
1895:
1896: /**
1897: * Protected convenience method to report an error to this Handler's
1898: * ErrorManager. This method will prefix all non null error messages with
1899: * <code>Level.SEVERE.getName()</code>. This allows the receiving error
1900: * manager to determine if the <code>msg</code> parameter is a simple error
1901: * message or a raw email message.
1902: *
1903: * @param msg a descriptive string (may be null)
1904: * @param ex an exception (may be null)
1905: * @param code an error code defined in ErrorManager
1906: */
1907: @Override
1908: protected void reportError(String msg, Exception ex, int code) {
1909: try {
1910: if (msg != null) {
1911: errorManager.error(Level.SEVERE.getName()
1912: .concat(": ").concat(msg), ex, code);
1913: } else {
1914: errorManager.error((String) null, ex, code);
1915: }
1916: } catch (RuntimeException | LinkageError GLASSFISH_21258) {
1917: reportLinkageError(GLASSFISH_21258, code);
1918: }
1919: }
1920:
1921: /**
1922: * Checks logging permissions if this handler has been sealed.
1923: * @throws SecurityException if a security manager exists and the caller
1924: * does not have {@code LoggingPermission("control")}.
1925: */
1926: private void checkAccess() {
1927: if (sealed) {
1928: LogManagerProperties.checkLogManagerAccess();
1929: }
1930: }
1931:
1932: /**
1933: * Determines the mimeType of a formatter from the getHead call.
1934: * This could be made protected, or a new class could be created to do
1935: * this type of conversion. Currently, this is only used for the body
1936: * since the attachments are computed by filename.
1937: * Package-private for unit testing.
1938: *
1939: * @param chunk any char sequence or null.
1940: * @return return the mime type or null for text/plain.
1941: */
1942: final String contentTypeOf(CharSequence chunk) {
1943: if (!isEmpty(chunk)) {
1944: final int MAX_CHARS = 25;
1945: if (chunk.length() > MAX_CHARS) {
1946: chunk = chunk.subSequence(0, MAX_CHARS);
1947: }
1948: try {
1949: final String charset = getEncodingName();
1950: final byte[] b = chunk.toString().getBytes(charset);
1951: final ByteArrayInputStream in = new ByteArrayInputStream(b);
1952: assert in.markSupported() : in.getClass().getName();
1953: return URLConnection.guessContentTypeFromStream(in);
1954: } catch (final IOException IOE) {
1955: reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
1956: }
1957: }
1958: return null; //text/plain
1959: }
1960:
1961: /**
1962: * Determines the mimeType of a formatter by the class name. This method
1963: * avoids calling getHead and getTail of content formatters during verify
1964: * because they might trigger side effects or excessive work. The name
1965: * formatters and subject are usually safe to call.
1966: * Package-private for unit testing.
1967: *
1968: * @param f the formatter or null.
1969: * @return return the mime type or null, meaning text/plain.
1970: * @since JavaMail 1.5.6
1971: */
1972: final String contentTypeOf(final Formatter f) {
1973: assert Thread.holdsLock(this);
1974: if (f != null) {
1975: String type = getContentType(f.getClass().getName());
1976: if (type != null) {
1977: return type;
1978: }
1979:
1980: for (Class<?> k = f.getClass(); k != Formatter.class;
1981: k = k.getSuperclass()) {
1982: String name;
1983: try {
1984: name = k.getSimpleName();
1985: } catch (final InternalError JDK8057919) {
1986: name = k.getName();
1987: }
1988: name = name.toLowerCase(Locale.ENGLISH);
1989: for (int idx = name.indexOf('$') + 1;
1990: (idx = name.indexOf("ml", idx)) > -1; idx += 2) {
1991: if (idx > 0) {
1992: if (name.charAt(idx - 1) == 'x') {
1993: return "application/xml";
1994: }
1995: if (idx > 1 && name.charAt(idx - 2) == 'h'
1996: && name.charAt(idx - 1) == 't') {
1997: return "text/html";
1998: }
1999: }
2000: }
2001: }
2002: }
2003: return null;
2004: }
2005:
2006: /**
2007: * Determines if the given throwable is a no content exception. It is
2008: * assumed Transport.sendMessage will call Message.writeTo so we need to
2009: * ignore any exceptions that could be layered on top of that call chain to
2010: * infer that sendMessage is failing because of writeTo. Package-private
2011: * for unit testing.
2012: *
2013: * @param msg the message without content.
2014: * @param t the throwable chain to test.
2015: * @return true if the throwable is a missing content exception.
2016: * @throws NullPointerException if any of the arguments are null.
2017: * @since JavaMail 1.4.5
2018: */
2019: @SuppressWarnings({"UseSpecificCatch", "ThrowableResultIgnored"})
2020: final boolean isMissingContent(Message msg, Throwable t) {
2021: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
2022: try {
2023: msg.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
2024: } catch (final RuntimeException RE) {
2025: throw RE; //Avoid catch all.
2026: } catch (final Exception noContent) {
2027: final String txt = noContent.getMessage();
2028: if (!isEmpty(txt)) {
2029: int limit = 0;
2030: while (t != null) {
2031: if (noContent.getClass() == t.getClass()
2032: && txt.equals(t.getMessage())) {
2033: return true;
2034: }
2035:
2036: //Not all Jakarta Mail implementations support JDK 1.4
2037: //exception chaining.
2038: final Throwable cause = t.getCause();
2039: if (cause == null && t instanceof MessagingException) {
2040: t = ((MessagingException) t).getNextException();
2041: } else {
2042: t = cause;
2043: }
2044:
2045: //Deal with excessive cause chains and cyclic throwables.
2046: if (++limit == (1 << 16)) {
2047: break; //Give up.
2048: }
2049: }
2050: }
2051: } finally {
2052: getAndSetContextClassLoader(ccl);
2053: }
2054: return false;
2055: }
2056:
2057: /**
2058: * Converts a mime message to a raw string or formats the reason
2059: * why message can't be changed to raw string and reports it.
2060: *
2061: * @param msg the mime message.
2062: * @param ex the original exception.
2063: * @param code the ErrorManager code.
2064: * @since JavaMail 1.4.5
2065: */
2066: @SuppressWarnings("UseSpecificCatch")
2067: private void reportError(Message msg, Exception ex, int code) {
2068: try {
2069: try { //Use direct call so we do not prefix raw email.
2070: errorManager.error(toRawString(msg), ex, code);
2071: } catch (final Exception e) {
2072: reportError(toMsgString(e), ex, code);
2073: }
2074: } catch (final LinkageError GLASSFISH_21258) {
2075: reportLinkageError(GLASSFISH_21258, code);
2076: }
2077: }
2078:
2079: /**
2080: * Reports the given linkage error or runtime exception.
2081: *
2082: * The current LogManager code will stop closing all remaining handlers if
2083: * an error is thrown during resetLogger. This is a workaround for
2084: * GLASSFISH-21258 and JDK-8152515.
2085: *
2086: * @param le the linkage error or a RuntimeException.
2087: * @param code the ErrorManager code.
2088: * @throws NullPointerException if error is null.
2089: * @since JavaMail 1.5.3
2090: */
2091: private void reportLinkageError(final Throwable le, final int code) {
2092: if (le == null) {
2093: throw new NullPointerException(String.valueOf(code));
2094: }
2095:
2096: final Integer idx = MUTEX.get();
2097: if (idx == null || idx > MUTEX_LINKAGE) {
2098: MUTEX.set(MUTEX_LINKAGE);
2099: try {
2100: Thread.currentThread().getUncaughtExceptionHandler()
2101: .uncaughtException(Thread.currentThread(), le);
2102: } catch (RuntimeException | LinkageError ignore) {
2103: } finally {
2104: if (idx != null) {
2105: MUTEX.set(idx);
2106: } else {
2107: MUTEX.remove();
2108: }
2109: }
2110: }
2111: }
2112:
2113: /**
2114: * Determines the mimeType from the given file name.
2115: * Used to override the body content type and used for all attachments.
2116: *
2117: * @param name the file name or class name.
2118: * @return the mime type or null for text/plain.
2119: */
2120: private String getContentType(final String name) {
2121: assert Thread.holdsLock(this);
2122: if (contentTypes == null) {
2123: return null;
2124: }
2125:
2126: final String type = contentTypes.getContentType(name);
2127: if ("application/octet-stream".equalsIgnoreCase(type)) {
2128: return null; //Formatters return strings, default to text/plain.
2129: }
2130: return type;
2131: }
2132:
2133: /**
2134: * Gets the encoding set for this handler, mime encoding, or file encoding.
2135: *
2136: * @return the java charset name, never null.
2137: * @since JavaMail 1.4.5
2138: */
2139: private String getEncodingName() {
2140: String charset = getEncoding();
2141: if (charset == null) {
2142: charset = MimeUtility.getDefaultJavaCharset();
2143: }
2144: return charset;
2145: }
2146:
2147: /**
2148: * Set the content for a part using the encoding assigned to the handler.
2149: *
2150: * @param part the part to assign.
2151: * @param buf the formatted data.
2152: * @param type the mime type or null, meaning text/plain.
2153: * @throws MessagingException if there is a problem.
2154: */
2155: private void setContent(MimePart part, CharSequence buf, String type) throws MessagingException {
2156: final String charset = getEncodingName();
2157: if (type != null && !"text/plain".equalsIgnoreCase(type)) {
2158: type = contentWithEncoding(type, charset);
2159: try {
2160: DataSource source = new ByteArrayDataSource(buf.toString(), type);
2161: part.setDataHandler(new DataHandler(source));
2162: } catch (final IOException IOE) {
2163: reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
2164: part.setText(buf.toString(), charset);
2165: }
2166: } else {
2167: part.setText(buf.toString(), MimeUtility.mimeCharset(charset));
2168: }
2169: }
2170:
2171: /**
2172: * Replaces the charset parameter with the current encoding.
2173: *
2174: * @param type the content type.
2175: * @param encoding the java charset name.
2176: * @return the type with a specified encoding.
2177: */
2178: private String contentWithEncoding(String type, String encoding) {
2179: assert encoding != null;
2180: try {
2181: final ContentType ct = new ContentType(type);
2182: ct.setParameter("charset", MimeUtility.mimeCharset(encoding));
2183: encoding = ct.toString(); //See jakarta.mail.internet.ContentType.
2184: if (!isEmpty(encoding)) { //Support pre K5687.
2185: type = encoding;
2186: }
2187: } catch (final MessagingException ME) {
2188: reportError(type, ME, ErrorManager.FORMAT_FAILURE);
2189: }
2190: return type;
2191: }
2192:
2193: /**
2194: * Sets the capacity for this handler.
2195: *
2196: * @param newCapacity the max number of records.
2197: * @throws SecurityException if a security manager exists and the
2198: * caller does not have <code>LoggingPermission("control")</code>.
2199: * @throws IllegalStateException if called from inside a push.
2200: */
2201: private synchronized void setCapacity0(int newCapacity) {
2202: checkAccess();
2203: if (newCapacity <= 0) {
2204: newCapacity = 1000;
2205: }
2206:
2207: if (isWriting) {
2208: throw new IllegalStateException();
2209: }
2210:
2211: if (this.capacity < 0) { //If closed, remain closed.
2212: this.capacity = -newCapacity;
2213: } else {
2214: this.push(false, ErrorManager.FLUSH_FAILURE);
2215: this.capacity = newCapacity;
2216: if (this.data != null && this.data.length > newCapacity) {
2217: this.data = Arrays.copyOf(data, newCapacity, LogRecord[].class);
2218: this.matched = Arrays.copyOf(matched, newCapacity);
2219: }
2220: }
2221: }
2222:
2223: /**
2224: * Gets the attachment filters using a happens-before relationship between
2225: * this method and setAttachmentFilters. The attachment filters are treated
2226: * as copy-on-write, so the returned array must never be modified or
2227: * published outside this class.
2228: *
2229: * @return a read only array of filters.
2230: */
2231: private Filter[] readOnlyAttachmentFilters() {
2232: return this.attachmentFilters;
2233: }
2234:
2235: /**
2236: * Factory for empty formatter arrays.
2237: *
2238: * @return an empty array.
2239: */
2240: private static Formatter[] emptyFormatterArray() {
2241: return EMPTY_FORMATTERS;
2242: }
2243:
2244: /**
2245: * Factory for empty filter arrays.
2246: *
2247: * @return an empty array.
2248: */
2249: private static Filter[] emptyFilterArray() {
2250: return EMPTY_FILTERS;
2251: }
2252:
2253: /**
2254: * Expand or shrink the attachment name formatters with the attachment
2255: * formatters.
2256: *
2257: * @return true if size was changed.
2258: */
2259: private boolean alignAttachmentNames(int expect) {
2260: assert Thread.holdsLock(this);
2261: boolean fixed = false;
2262: final int current = this.attachmentNames.length;
2263: if (current != expect) {
2264: this.attachmentNames = Arrays.copyOf(attachmentNames, expect,
2265: Formatter[].class);
2266: fixed = current != 0;
2267: }
2268:
2269: //Copy of zero length array is cheap, warm up copyOf.
2270: if (expect == 0) {
2271: this.attachmentNames = emptyFormatterArray();
2272: assert this.attachmentNames.length == 0;
2273: } else {
2274: for (int i = 0; i < expect; ++i) {
2275: if (this.attachmentNames[i] == null) {
2276: this.attachmentNames[i] = TailNameFormatter.of(
2277: toString(this.attachmentFormatters[i]));
2278: }
2279: }
2280: }
2281: return fixed;
2282: }
2283:
2284: private boolean alignAttachmentFormatters(int expect) {
2285: assert Thread.holdsLock(this);
2286: boolean fixed = false;
2287: final int current = this.attachmentFormatters.length;
2288: if (current != expect) {
2289: this.attachmentFormatters = Arrays.copyOf(attachmentFormatters, expect,
2290: Formatter[].class);
2291: fixed = current != 0;
2292: }
2293:
2294: //Copy of zero length array is cheap, warm up copyOf.
2295: if (expect == 0) {
2296: this.attachmentFormatters = emptyFormatterArray();
2297: assert this.attachmentFormatters.length == 0;
2298: } else {
2299: for (int i = current; i < expect; ++i) {
2300: if (this.attachmentFormatters[i] == null) {
2301: this.attachmentFormatters[i] = createSimpleFormatter();
2302: }
2303: }
2304: }
2305: return fixed;
2306: }
2307:
2308: /**
2309: * Expand or shrink the attachment filters with the attachment formatters.
2310: *
2311: * @return true if the size was changed.
2312: */
2313: private boolean alignAttachmentFilters(int expect) {
2314: assert Thread.holdsLock(this);
2315:
2316: boolean fixed = false;
2317: final int current = this.attachmentFilters.length;
2318: if (current != expect) {
2319: this.attachmentFilters = Arrays.copyOf(attachmentFilters, expect,
2320: Filter[].class);
2321: clearMatches(Math.min(current, expect));
2322: fixed = current != 0;
2323:
2324: //Array elements default to null so skip filling if body filter
2325: //is null. If not null then only assign to expanded elements.
2326: final Filter body = this.filter;
2327: if (body != null) {
2328: for (int i = current; i < expect; ++i) {
2329: this.attachmentFilters[i] = body;
2330: }
2331: }
2332: }
2333:
2334: //Copy of zero length array is cheap, warm up copyOf.
2335: if (expect == 0) {
2336: this.attachmentFilters = emptyFilterArray();
2337: assert this.attachmentFilters.length == 0;
2338: }
2339: return fixed;
2340: }
2341:
2342: /**
2343: * Sets the size to zero and clears the current buffer.
2344: */
2345: private void reset() {
2346: assert Thread.holdsLock(this);
2347: if (size < data.length) {
2348: Arrays.fill(data, 0, size, null);
2349: } else {
2350: Arrays.fill(data, null);
2351: }
2352: this.size = 0;
2353: }
2354:
2355: /**
2356: * Expands the internal buffer up to the capacity.
2357: */
2358: private void grow() {
2359: assert Thread.holdsLock(this);
2360: final int len = data.length;
2361: int newCapacity = len + (len >> 1) + 1;
2362: if (newCapacity > capacity || newCapacity < len) {
2363: newCapacity = capacity;
2364: }
2365: assert len != capacity : len;
2366: this.data = Arrays.copyOf(data, newCapacity, LogRecord[].class);
2367: this.matched = Arrays.copyOf(matched, newCapacity);
2368: }
2369:
2370: /**
2371: * Configures the handler properties from the log manager.
2372: *
2373: * @param props the given mail properties. Maybe null and are never
2374: * captured by this handler.
2375: * @throws SecurityException if a security manager exists and the
2376: * caller does not have <code>LoggingPermission("control")</code>.
2377: */
2378: private synchronized void init(final Properties props) {
2379: assert this.errorManager != null;
2380: final String p = getClass().getName();
2381: this.mailProps = new Properties(); //See method param comments.
2382: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
2383: try {
2384: this.contentTypes = FileTypeMap.getDefaultFileTypeMap();
2385: } finally {
2386: getAndSetContextClassLoader(ccl);
2387: }
2388:
2389: //Assign any custom error manager first so it can detect all failures.
2390: initErrorManager(fromLogManager(p.concat(".errorManager")));
2391: initCapacity(fromLogManager(p.concat(".capacity")));
2392: initLevel(fromLogManager(p.concat(".level")));
2393: initEnabled(fromLogManager(p.concat(".enabled")));
2394: initFilter(fromLogManager(p.concat(".filter")));
2395: this.auth = newAuthenticator(fromLogManager(p.concat(".authenticator")));
2396:
2397: initEncoding(fromLogManager(p.concat(".encoding")));
2398: initFormatter(fromLogManager(p.concat(".formatter")));
2399: initComparator(fromLogManager(p.concat(".comparator")));
2400: initComparatorReverse(fromLogManager(p.concat(".comparator.reverse")));
2401: initPushLevel(fromLogManager(p.concat(".pushLevel")));
2402: initPushFilter(fromLogManager(p.concat(".pushFilter")));
2403:
2404: initSubject(fromLogManager(p.concat(".subject")));
2405:
2406: initAttachmentFormaters(fromLogManager(p.concat(".attachment.formatters")));
2407: initAttachmentFilters(fromLogManager(p.concat(".attachment.filters")));
2408: initAttachmentNames(fromLogManager(p.concat(".attachment.names")));
2409:
2410: if (props == null && fromLogManager(p.concat(".verify")) != null) {
2411: verifySettings(initSession());
2412: }
2413: intern(); //Show verify warnings first.
2414: }
2415:
2416: /**
2417: * Interns the error manager, formatters, and filters contained in this
2418: * handler. The comparator is not interned. This method can only be
2419: * called from init after all of formatters and filters are in a constructed
2420: * and in a consistent state.
2421: *
2422: * @since JavaMail 1.5.0
2423: */
2424: private void intern() {
2425: assert Thread.holdsLock(this);
2426: try {
2427: Object canidate;
2428: Object result;
2429: final Map<Object, Object> seen = new HashMap<>();
2430: try {
2431: intern(seen, this.errorManager);
2432: } catch (final SecurityException se) {
2433: reportError(se.getMessage(), se, ErrorManager.OPEN_FAILURE);
2434: }
2435:
2436: try {
2437: canidate = this.filter;
2438: result = intern(seen, canidate);
2439: if (result != canidate && result instanceof Filter) {
2440: this.filter = (Filter) result;
2441: }
2442:
2443: canidate = this.formatter;
2444: result = intern(seen, canidate);
2445: if (result != canidate && result instanceof Formatter) {
2446: this.formatter = (Formatter) result;
2447: }
2448: } catch (final SecurityException se) {
2449: reportError(se.getMessage(), se, ErrorManager.OPEN_FAILURE);
2450: }
2451:
2452: canidate = this.subjectFormatter;
2453: result = intern(seen, canidate);
2454: if (result != canidate && result instanceof Formatter) {
2455: this.subjectFormatter = (Formatter) result;
2456: }
2457:
2458: canidate = this.pushFilter;
2459: result = intern(seen, canidate);
2460: if (result != canidate && result instanceof Filter) {
2461: this.pushFilter = (Filter) result;
2462: }
2463:
2464: for (int i = 0; i < attachmentFormatters.length; ++i) {
2465: canidate = attachmentFormatters[i];
2466: result = intern(seen, canidate);
2467: if (result != canidate && result instanceof Formatter) {
2468: attachmentFormatters[i] = (Formatter) result;
2469: }
2470:
2471: canidate = attachmentFilters[i];
2472: result = intern(seen, canidate);
2473: if (result != canidate && result instanceof Filter) {
2474: attachmentFilters[i] = (Filter) result;
2475: }
2476:
2477: canidate = attachmentNames[i];
2478: result = intern(seen, canidate);
2479: if (result != canidate && result instanceof Formatter) {
2480: attachmentNames[i] = (Formatter) result;
2481: }
2482: }
2483: } catch (final Exception skip) {
2484: reportError(skip.getMessage(), skip, ErrorManager.OPEN_FAILURE);
2485: } catch (final LinkageError skip) {
2486: reportError(skip.getMessage(), new InvocationTargetException(skip),
2487: ErrorManager.OPEN_FAILURE);
2488: }
2489: }
2490:
2491: /**
2492: * If possible performs an intern of the given object into the
2493: * map. If the object can not be interned the given object is returned.
2494: *
2495: * @param m the map used to record the interned values.
2496: * @param o the object to try an intern.
2497: * @return the original object or an intern replacement.
2498: * @throws SecurityException if this operation is not allowed by the
2499: * security manager.
2500: * @throws Exception if there is an unexpected problem.
2501: * @since JavaMail 1.5.0
2502: */
2503: private Object intern(Map<Object, Object> m, Object o) throws Exception {
2504: if (o == null) {
2505: return null;
2506: }
2507:
2508: /**
2509: * The common case is that most objects will not intern. The given
2510: * object has a public no argument constructor or is an instance of a
2511: * TailNameFormatter. TailNameFormatter is safe use as a map key.
2512: * For everything else we create a clone of the given object.
2513: * This is done because of the following:
2514: * 1. Clones can be used to test that a class provides an equals method
2515: * and that the equals method works correctly.
2516: * 2. Calling equals on the given object is assumed to be cheap.
2517: * 3. The intern map can be filtered so it only contains objects that
2518: * can be interned, which reduces the memory footprint.
2519: * 4. Clones are method local garbage.
2520: * 5. Hash code is only called on the clones so bias locking is not
2521: * disabled on the objects the handler will use.
2522: */
2523: final Object key;
2524: if (o.getClass().getName().equals(TailNameFormatter.class.getName())) {
2525: key = o;
2526: } else {
2527: //This call was already made in the LogManagerProperties so this
2528: //shouldn't trigger loading of any lazy reflection code.
2529: key = o.getClass().getConstructor().newInstance();
2530: }
2531:
2532: final Object use;
2533: //Check the classloaders of each object avoiding the security manager.
2534: if (key.getClass() == o.getClass()) {
2535: Object found = m.get(key); //Transitive equals test.
2536: if (found == null) {
2537: //Ensure that equals is symmetric to prove intern is safe.
2538: final boolean right = key.equals(o);
2539: final boolean left = o.equals(key);
2540: if (right && left) {
2541: //Assume hashCode is defined at this point.
2542: found = m.put(o, o);
2543: if (found != null) {
2544: reportNonDiscriminating(key, found);
2545: found = m.remove(key);
2546: if (found != o) {
2547: reportNonDiscriminating(key, found);
2548: m.clear(); //Try to restore order.
2549: }
2550: }
2551: } else {
2552: if (right != left) {
2553: reportNonSymmetric(o, key);
2554: }
2555: }
2556: use = o;
2557: } else {
2558: //Check for a discriminating equals method.
2559: if (o.getClass() == found.getClass()) {
2560: use = found;
2561: } else {
2562: reportNonDiscriminating(o, found);
2563: use = o;
2564: }
2565: }
2566: } else {
2567: use = o;
2568: }
2569: return use;
2570: }
2571:
2572: /**
2573: * Factory method used to create a java.util.logging.SimpleFormatter.
2574: *
2575: * @return a new SimpleFormatter.
2576: * @since JavaMail 1.5.6
2577: */
2578: private static Formatter createSimpleFormatter() {
2579: //Don't force the byte code verifier to load the formatter.
2580: return Formatter.class.cast(new SimpleFormatter());
2581: }
2582:
2583: /**
2584: * Checks a char sequence value for null or empty.
2585: *
2586: * @param s the char sequence.
2587: * @return true if the given string is null or zero length.
2588: */
2589: private static boolean isEmpty(final CharSequence s) {
2590: return s == null || s.length() == 0;
2591: }
2592:
2593: /**
2594: * Checks that a string is not empty and not equal to the literal "null".
2595: *
2596: * @param name the string to check for a value.
2597: * @return true if the string has a valid value.
2598: */
2599: private static boolean hasValue(final String name) {
2600: return !isEmpty(name) && !"null".equalsIgnoreCase(name);
2601: }
2602:
2603: /**
2604: * Parses LogManager string values into objects used by this handler.
2605: *
2606: * @param list the list of attachment filter class names.
2607: * @throws SecurityException if not allowed.
2608: */
2609: private void initAttachmentFilters(final String list) {
2610: assert Thread.holdsLock(this);
2611: assert this.attachmentFormatters != null;
2612: if (!isEmpty(list)) {
2613: final String[] names = list.split(",");
2614: Filter[] a = new Filter[names.length];
2615: for (int i = 0; i < a.length; ++i) {
2616: names[i] = names[i].trim();
2617: if (!"null".equalsIgnoreCase(names[i])) {
2618: try {
2619: a[i] = LogManagerProperties.newFilter(names[i]);
2620: } catch (final SecurityException SE) {
2621: throw SE; //Avoid catch all.
2622: } catch (final Exception E) {
2623: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2624: }
2625: }
2626: }
2627:
2628: this.attachmentFilters = a;
2629: if (alignAttachmentFilters(attachmentFormatters.length)) {
2630: reportError("Attachment filters.",
2631: attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
2632: }
2633: } else {
2634: this.attachmentFilters = emptyFilterArray();
2635: alignAttachmentFilters(attachmentFormatters.length);
2636: }
2637: }
2638:
2639: /**
2640: * Parses LogManager string values into objects used by this handler.
2641: *
2642: * @param list the list of attachment formatter class names or literal names.
2643: * @throws SecurityException if not allowed.
2644: */
2645: private void initAttachmentFormaters(final String list) {
2646: assert Thread.holdsLock(this);
2647: if (!isEmpty(list)) {
2648: final Formatter[] a;
2649: final String[] names = list.split(",");
2650: if (names.length == 0) {
2651: a = emptyFormatterArray();
2652: } else {
2653: a = new Formatter[names.length];
2654: }
2655:
2656: for (int i = 0; i < a.length; ++i) {
2657: names[i] = names[i].trim();
2658: if (!"null".equalsIgnoreCase(names[i])) {
2659: try {
2660: a[i] = LogManagerProperties.newFormatter(names[i]);
2661: if (a[i] instanceof TailNameFormatter) {
2662: final Exception CNFE = new ClassNotFoundException(a[i].toString());
2663: reportError("Attachment formatter.", CNFE, ErrorManager.OPEN_FAILURE);
2664: a[i] = createSimpleFormatter();
2665: }
2666: } catch (final SecurityException SE) {
2667: throw SE; //Avoid catch all.
2668: } catch (final Exception E) {
2669: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2670: a[i] = createSimpleFormatter();
2671: }
2672: } else {
2673: a[i] = createSimpleFormatter();
2674: }
2675: }
2676:
2677: this.attachmentFormatters = a;
2678: } else {
2679: this.attachmentFormatters = emptyFormatterArray();
2680: }
2681: }
2682:
2683: /**
2684: * Parses LogManager string values into objects used by this handler.
2685: *
2686: * @param list of formatter class names or literals.
2687: * @throws SecurityException if not allowed.
2688: */
2689: private void initAttachmentNames(final String list) {
2690: assert Thread.holdsLock(this);
2691: assert this.attachmentFormatters != null;
2692:
2693: if (!isEmpty(list)) {
2694: final String[] names = list.split(",");
2695: final Formatter[] a = new Formatter[names.length];
2696: for (int i = 0; i < a.length; ++i) {
2697: names[i] = names[i].trim();
2698: if (!"null".equalsIgnoreCase(names[i])) {
2699: try {
2700: try {
2701: a[i] = LogManagerProperties.newFormatter(names[i]);
2702: } catch (ClassNotFoundException
2703: | ClassCastException literal) {
2704: a[i] = TailNameFormatter.of(names[i]);
2705: }
2706: } catch (final SecurityException SE) {
2707: throw SE; //Avoid catch all.
2708: } catch (final Exception E) {
2709: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2710: }
2711: } else {
2712: a[i] = TailNameFormatter.of(toString(attachmentFormatters[i]));
2713: }
2714: }
2715:
2716: this.attachmentNames = a;
2717: if (alignAttachmentNames(attachmentFormatters.length)) {
2718: reportError("Attachment names.",
2719: attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
2720: }
2721: } else {
2722: this.attachmentNames = emptyFormatterArray();
2723: alignAttachmentNames(attachmentFormatters.length);
2724: }
2725: }
2726:
2727: /**
2728: * Parses LogManager string values into objects used by this handler.
2729: *
2730: * @param name the authenticator class name, literal password, or empty string.
2731: * @throws SecurityException if not allowed.
2732: */
2733: private Authenticator newAuthenticator(final String name) {
2734: Authenticator a = null;
2735: if (name != null && !"null".equalsIgnoreCase(name)) {
2736: if (!name.isEmpty()) {
2737: try {
2738: a = LogManagerProperties
2739: .newObjectFrom(name, Authenticator.class);
2740: } catch (final SecurityException SE) {
2741: throw SE;
2742: } catch (final ClassNotFoundException
2743: | ClassCastException literalAuth) {
2744: a = DefaultAuthenticator.of(name);
2745: } catch (final Exception E) {
2746: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2747: } catch (final LinkageError JDK8152515) {
2748: reportLinkageError(JDK8152515, ErrorManager.OPEN_FAILURE);
2749: }
2750: } else { //Authenticator is installed to provide the user name.
2751: a = DefaultAuthenticator.of(name);
2752: }
2753: }
2754: return a;
2755: }
2756:
2757: /**
2758: * Parses LogManager string values into objects used by this handler.
2759: *
2760: * @param nameOrNumber the level name or number.
2761: * @throws SecurityException if not allowed.
2762: */
2763: private void initLevel(final String nameOrNumber) {
2764: assert Thread.holdsLock(this);
2765: assert disabledLevel == null : disabledLevel;
2766: try {
2767: if (!isEmpty(nameOrNumber)) {
2768: logLevel = Level.parse(nameOrNumber);
2769: } else {
2770: logLevel = Level.WARNING;
2771: }
2772: } catch (final SecurityException SE) {
2773: throw SE; //Avoid catch all.
2774: } catch (final RuntimeException RE) {
2775: reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
2776: logLevel = Level.WARNING;
2777: }
2778: }
2779:
2780: /**
2781: * Disables this handler if the property was specified in LogManager.
2782: * Assumes that initLevel was called before this method.
2783: *
2784: * @param enabled the string false will only disable this handler.
2785: * @since Angus Mail 2.0.3
2786: */
2787: private void initEnabled(final String enabled) {
2788: assert Thread.holdsLock(this);
2789: assert logLevel != null;
2790: assert capacity != 0;
2791: //By default the Handler is enabled so only need to disable it on init.
2792: if (hasValue(enabled) && !Boolean.parseBoolean(enabled)) {
2793: setEnabled0(false);
2794: }
2795: }
2796:
2797: /**
2798: * Parses LogManager string values into objects used by this handler.
2799: *
2800: * @param name the filter class name or null.
2801: * @throws SecurityException if not allowed.
2802: */
2803: private void initFilter(final String name) {
2804: assert Thread.holdsLock(this);
2805: try {
2806: if (hasValue(name)) {
2807: filter = LogManagerProperties.newFilter(name);
2808: } else {
2809: filter = null;
2810: }
2811: } catch (final SecurityException SE) {
2812: throw SE; //Avoid catch all.
2813: } catch (final Exception E) {
2814: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2815: }
2816: }
2817:
2818: /**
2819: * Parses LogManager string values into objects used by this handler.
2820: *
2821: * @param value the capacity value.
2822: * @throws SecurityException if not allowed.
2823: */
2824: private void initCapacity(final String value) {
2825: assert Thread.holdsLock(this);
2826: final int DEFAULT_CAPACITY = 1000;
2827: try {
2828: if (value != null) {
2829: this.setCapacity0(Integer.parseInt(value));
2830: } else {
2831: this.setCapacity0(DEFAULT_CAPACITY);
2832: }
2833: } catch (final SecurityException SE) {
2834: throw SE; //Avoid catch all.
2835: } catch (final RuntimeException RE) {
2836: reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
2837: }
2838:
2839: if (capacity <= 0) {
2840: capacity = DEFAULT_CAPACITY;
2841: }
2842:
2843: this.data = new LogRecord[1];
2844: this.matched = new int[this.data.length];
2845: }
2846:
2847: /**
2848: * Sets the encoding of this handler.
2849: *
2850: * @param e the encoding name or null.
2851: * @throws SecurityException if not allowed.
2852: */
2853: private void initEncoding(final String e) {
2854: assert Thread.holdsLock(this);
2855: try {
2856: setEncoding0(e);
2857: } catch (final SecurityException SE) {
2858: throw SE; //Avoid catch all.
2859: } catch (UnsupportedEncodingException | RuntimeException UEE) {
2860: reportError(UEE.getMessage(), UEE, ErrorManager.OPEN_FAILURE);
2861: }
2862: }
2863:
2864: /**
2865: * Used to get or create the default ErrorManager used before init.
2866: *
2867: * @return the super error manager or a new ErrorManager.
2868: * @since JavaMail 1.5.3
2869: */
2870: private ErrorManager defaultErrorManager() {
2871: ErrorManager em;
2872: try { //Try to share the super error manager.
2873: em = super.getErrorManager();
2874: } catch (RuntimeException | LinkageError ignore) {
2875: em = null;
2876: }
2877:
2878: //Don't assume that the super call is not null.
2879: if (em == null) {
2880: em = new ErrorManager();
2881: }
2882: return em;
2883: }
2884:
2885: /**
2886: * Creates the error manager for this handler.
2887: *
2888: * @param name the error manager class name.
2889: * @throws SecurityException if not allowed.
2890: */
2891: private void initErrorManager(final String name) {
2892: assert Thread.holdsLock(this);
2893: try {
2894: if (name != null) {
2895: setErrorManager0(LogManagerProperties.newErrorManager(name));
2896: }
2897: } catch (final SecurityException SE) {
2898: throw SE; //Avoid catch all.
2899: } catch (final Exception E) {
2900: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2901: }
2902: }
2903:
2904: /**
2905: * Parses LogManager string values into objects used by this handler.
2906: *
2907: * @param name the formatter class name or null.
2908: * @throws SecurityException if not allowed.
2909: */
2910: private void initFormatter(final String name) {
2911: assert Thread.holdsLock(this);
2912: try {
2913: if (hasValue(name)) {
2914: final Formatter f
2915: = LogManagerProperties.newFormatter(name);
2916: assert f != null;
2917: if (f instanceof TailNameFormatter == false) {
2918: formatter = f;
2919: } else {
2920: formatter = createSimpleFormatter();
2921: }
2922: } else {
2923: formatter = createSimpleFormatter();
2924: }
2925: } catch (final SecurityException SE) {
2926: throw SE; //Avoid catch all.
2927: } catch (final Exception E) {
2928: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2929: formatter = createSimpleFormatter();
2930: }
2931: }
2932:
2933: /**
2934: * Creates the comparator for this handler.
2935: *
2936: * @param p the handler class name used as the prefix.
2937: * @throws SecurityException if not allowed.
2938: */
2939: private void initComparator(final String name) {
2940: assert Thread.holdsLock(this);
2941: try {
2942: if (hasValue(name)) {
2943: comparator = LogManagerProperties.newComparator(name);
2944: } else {
2945: comparator = null;
2946: }
2947: } catch (final SecurityException SE) {
2948: throw SE; //Avoid catch all.
2949: } catch (final Exception E) {
2950: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2951: }
2952: }
2953:
2954:
2955: private void initComparatorReverse(final String reverse) {
2956: if (Boolean.parseBoolean(reverse)) {
2957: if (comparator != null) {
2958: comparator = LogManagerProperties.reverseOrder(comparator);
2959: } else {
2960: IllegalArgumentException E = new IllegalArgumentException(
2961: "No comparator to reverse.");
2962: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
2963: }
2964: }
2965: }
2966:
2967: /**
2968: * Parses LogManager string values into objects used by this handler.
2969: *
2970: * @param nameOrNumber the level name, number, or null for OFF.
2971: * @throws SecurityException if not allowed.
2972: */
2973: private void initPushLevel(final String nameOrNumber) {
2974: assert Thread.holdsLock(this);
2975: try {
2976: if (!isEmpty(nameOrNumber)) {
2977: this.pushLevel = Level.parse(nameOrNumber);
2978: } else {
2979: this.pushLevel = Level.OFF;
2980: }
2981: } catch (final RuntimeException RE) {
2982: reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
2983: }
2984:
2985: if (this.pushLevel == null) {
2986: this.pushLevel = Level.OFF;
2987: }
2988: }
2989:
2990: /**
2991: * Parses LogManager string values into objects used by this handler.
2992: *
2993: * @param name the push filter class name.
2994: * @throws SecurityException if not allowed.
2995: */
2996: private void initPushFilter(final String name) {
2997: assert Thread.holdsLock(this);
2998: try {
2999: if (hasValue(name)) {
3000: this.pushFilter = LogManagerProperties.newFilter(name);
3001: } else {
3002: this.pushFilter = null;
3003: }
3004: } catch (final SecurityException SE) {
3005: throw SE; //Avoid catch all.
3006: } catch (final Exception E) {
3007: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
3008: }
3009: }
3010:
3011: /**
3012: * Creates the subject formatter used by this handler.
3013: *
3014: * @param name the formatter class name, string literal, or null.
3015: * @throws SecurityException if not allowed.
3016: */
3017: private void initSubject(String name) {
3018: assert Thread.holdsLock(this);
3019: if (isWriting) {
3020: throw new IllegalStateException();
3021: }
3022:
3023: if (name == null) { //Soft dependency on CollectorFormatter.
3024: name = "org.eclipse.angus.mail.util.logging.CollectorFormatter";
3025: }
3026:
3027: if (hasValue(name)) {
3028: try {
3029: this.subjectFormatter = LogManagerProperties.newFormatter(name);
3030: } catch (final SecurityException SE) {
3031: throw SE; //Avoid catch all.
3032: } catch (ClassNotFoundException
3033: | ClassCastException literalSubject) {
3034: this.subjectFormatter = TailNameFormatter.of(name);
3035: } catch (final Exception E) {
3036: this.subjectFormatter = TailNameFormatter.of(name);
3037: reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
3038: }
3039: } else { //User has forced empty or literal null.
3040: this.subjectFormatter = TailNameFormatter.of(name);
3041: }
3042: }
3043:
3044: /**
3045: * Check if any attachment would actually format the given
3046: * <code>LogRecord</code>. This method does not check if the handler
3047: * is level is set to OFF or if the handler is closed.
3048: *
3049: * @param record a <code>LogRecord</code>
3050: * @return true if the <code>LogRecord</code> would be formatted.
3051: */
3052: private boolean isAttachmentLoggable(final LogRecord record) {
3053: final Filter[] filters = readOnlyAttachmentFilters();
3054: for (int i = 0; i < filters.length; ++i) {
3055: final Filter f = filters[i];
3056: if (f == null || f.isLoggable(record)) {
3057: setMatchedPart(i);
3058: return true;
3059: }
3060: }
3061: return false;
3062: }
3063:
3064: /**
3065: * Check if this <code>Handler</code> would push after storing the
3066: * <code>LogRecord</code> into its internal buffer.
3067: *
3068: * @param record a <code>LogRecord</code>
3069: * @return true if the <code>LogRecord</code> triggers an email push.
3070: * @throws NullPointerException if tryMutex was not called.
3071: */
3072: private boolean isPushable(final LogRecord record) {
3073: assert Thread.holdsLock(this);
3074: final int value = getPushLevel().intValue();
3075: if (value == offValue || record.getLevel().intValue() < value) {
3076: return false;
3077: }
3078:
3079: final Filter push = getPushFilter();
3080: if (push == null) {
3081: return true;
3082: }
3083:
3084: final int match = getMatchedPart();
3085: if ((match == -1 && getFilter() == push)
3086: || (match >= 0 && attachmentFilters[match] == push)) {
3087: return true;
3088: } else {
3089: return push.isLoggable(record);
3090: }
3091: }
3092:
3093: /**
3094: * Used to perform push or flush.
3095: *
3096: * @param priority true for high priority otherwise false for normal.
3097: * @param code the error manager code.
3098: */
3099: private void push(final boolean priority, final int code) {
3100: if (tryMutex()) {
3101: try {
3102: final Message msg = writeLogRecords(code);
3103: if (msg != null) {
3104: send(msg, priority, code);
3105: }
3106: } catch (final LinkageError JDK8152515) {
3107: reportLinkageError(JDK8152515, code);
3108: } finally {
3109: releaseMutex();
3110: }
3111: } else {
3112: reportUnPublishedError(null);
3113: }
3114: }
3115:
3116: /**
3117: * Used to send the generated email or write its contents to the
3118: * error manager for this handler. This method does not hold any
3119: * locks so new records can be added to this handler during a send or
3120: * failure.
3121: *
3122: * @param msg the message or null.
3123: * @param priority true for high priority or false for normal.
3124: * @param code the ErrorManager code.
3125: * @throws NullPointerException if message is null.
3126: */
3127: private void send(Message msg, boolean priority, int code) {
3128: try {
3129: envelopeFor(msg, priority);
3130: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3131: try { //JDK-8025251
3132: Transport.send(msg); //Calls save changes.
3133: } finally {
3134: getAndSetContextClassLoader(ccl);
3135: }
3136: } catch (final Exception e) {
3137: reportError(msg, e, code);
3138: }
3139: }
3140:
3141: /**
3142: * Performs a sort on the records if needed.
3143: * Any exception thrown during a sort is considered a formatting error.
3144: */
3145: private void sort() {
3146: assert Thread.holdsLock(this);
3147: if (comparator != null) {
3148: try {
3149: if (size != 1) {
3150: Arrays.sort(data, 0, size, comparator);
3151: } else {
3152: if (comparator.compare(data[0], data[0]) != 0) {
3153: throw new IllegalArgumentException(
3154: comparator.getClass().getName());
3155: }
3156: }
3157: } catch (final RuntimeException RE) {
3158: reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
3159: }
3160: }
3161: }
3162:
3163: /**
3164: * Formats all records in the buffer and places the output in a Message.
3165: * This method under most conditions will catch, report, and continue when
3166: * exceptions occur. This method holds a lock on this handler.
3167: *
3168: * @param code the error manager code.
3169: * @return null if there are no records or is currently in a push.
3170: * Otherwise a new message is created with a formatted message and
3171: * attached session.
3172: */
3173: private Message writeLogRecords(final int code) {
3174: try {
3175: synchronized (this) {
3176: if (size > 0 && !isWriting) {
3177: isWriting = true;
3178: try {
3179: return writeLogRecords0();
3180: } finally {
3181: isWriting = false;
3182: if (size > 0) {
3183: reset();
3184: }
3185: }
3186: }
3187: }
3188: } catch (final Exception e) {
3189: reportError(e.getMessage(), e, code);
3190: }
3191: return null;
3192: }
3193:
3194: /**
3195: * Formats all records in the buffer and places the output in a Message.
3196: * This method under most conditions will catch, report, and continue when
3197: * exceptions occur.
3198: *
3199: * @return null if there are no records or is currently in a push. Otherwise
3200: * a new message is created with a formatted message and attached session.
3201: * @throws MessagingException if there is a problem.
3202: * @throws IOException if there is a problem.
3203: * @throws RuntimeException if there is an unexpected problem.
3204: * @since JavaMail 1.5.3
3205: */
3206: private Message writeLogRecords0() throws Exception {
3207: assert Thread.holdsLock(this);
3208: sort();
3209: if (session == null) {
3210: initSession();
3211: }
3212: MimeMessage msg = new MimeMessage(session);
3213:
3214: /**
3215: * Parts are lazily created when an attachment performs a getHead
3216: * call. Therefore, a null part at an index means that the head is
3217: * required.
3218: */
3219: MimeBodyPart[] parts = new MimeBodyPart[attachmentFormatters.length];
3220:
3221: /**
3222: * The buffers are lazily created when the part requires a getHead.
3223: */
3224: StringBuilder[] buffers = new StringBuilder[parts.length];
3225: StringBuilder buf = null;
3226: final MimePart body;
3227: if (parts.length == 0) {
3228: msg.setDescription(descriptionFrom(
3229: getFormatter(), getFilter(), subjectFormatter));
3230: body = msg;
3231: } else {
3232: msg.setDescription(descriptionFrom(
3233: comparator, pushLevel, pushFilter));
3234: body = createBodyPart();
3235: }
3236:
3237: appendSubject(msg, head(subjectFormatter));
3238: final Formatter bodyFormat = getFormatter();
3239: final Filter bodyFilter = getFilter();
3240:
3241: Locale lastLocale = null;
3242: for (int ix = 0; ix < size; ++ix) {
3243: boolean formatted = false;
3244: final int match = matched[ix];
3245: final LogRecord r = data[ix];
3246: data[ix] = null; //Clear while formatting.
3247:
3248: final Locale locale = localeFor(r);
3249: appendSubject(msg, format(subjectFormatter, r));
3250: Filter lmf = null; //Identity of last matched filter.
3251: if (bodyFilter == null || match == -1 || parts.length == 0
3252: || (match < -1 && bodyFilter.isLoggable(r))) {
3253: lmf = bodyFilter;
3254: if (buf == null) {
3255: buf = new StringBuilder();
3256: buf.append(head(bodyFormat));
3257: }
3258: formatted = true;
3259: buf.append(format(bodyFormat, r));
3260: if (locale != null && !locale.equals(lastLocale)) {
3261: appendContentLang(body, locale);
3262: }
3263: }
3264:
3265: for (int i = 0; i < parts.length; ++i) {
3266: //A match index less than the attachment index means that
3267: //the filter has not seen this record.
3268: final Filter af = attachmentFilters[i];
3269: if (af == null || lmf == af || match == i
3270: || (match < i && af.isLoggable(r))) {
3271: if (lmf == null && af != null) {
3272: lmf = af;
3273: }
3274: if (parts[i] == null) {
3275: parts[i] = createBodyPart(i);
3276: buffers[i] = new StringBuilder();
3277: buffers[i].append(head(attachmentFormatters[i]));
3278: appendFileName(parts[i], head(attachmentNames[i]));
3279: }
3280: formatted = true;
3281: appendFileName(parts[i], format(attachmentNames[i], r));
3282: buffers[i].append(format(attachmentFormatters[i], r));
3283: if (locale != null && !locale.equals(lastLocale)) {
3284: appendContentLang(parts[i], locale);
3285: }
3286: }
3287: }
3288:
3289: if (formatted) {
3290: if (body != msg && locale != null
3291: && !locale.equals(lastLocale)) {
3292: appendContentLang(msg, locale);
3293: }
3294: } else { //Belongs to no mime part.
3295: reportFilterError(r);
3296: }
3297: lastLocale = locale;
3298: }
3299: this.size = 0;
3300:
3301: for (int i = parts.length - 1; i >= 0; --i) {
3302: if (parts[i] != null) {
3303: appendFileName(parts[i], tail(attachmentNames[i], "err"));
3304: buffers[i].append(tail(attachmentFormatters[i], ""));
3305:
3306: if (buffers[i].length() > 0) {
3307: String name = parts[i].getFileName();
3308: if (isEmpty(name)) { //Exceptional case.
3309: name = toString(attachmentFormatters[i]);
3310: parts[i].setFileName(name);
3311: }
3312: setContent(parts[i], buffers[i], getContentType(name));
3313: } else {
3314: setIncompleteCopy(msg);
3315: parts[i] = null; //Skip this part.
3316: }
3317: buffers[i] = null;
3318: }
3319: }
3320:
3321: if (buf != null) {
3322: buf.append(tail(bodyFormat, ""));
3323: //This body part is always added, even if the buffer is empty,
3324: //so the body is never considered an incomplete-copy.
3325: } else {
3326: buf = new StringBuilder(0);
3327: }
3328:
3329: appendSubject(msg, tail(subjectFormatter, ""));
3330:
3331: String contentType = contentTypeOf(buf);
3332: String altType = contentTypeOf(bodyFormat);
3333: setContent(body, buf, altType == null ? contentType : altType);
3334: if (body != msg) {
3335: final MimeMultipart multipart = createMultipart();
3336: //assert body instanceof BodyPart : body;
3337: multipart.addBodyPart((BodyPart) body);
3338:
3339: for (int i = 0; i < parts.length; ++i) {
3340: if (parts[i] != null) {
3341: multipart.addBodyPart(parts[i]);
3342: }
3343: }
3344: msg.setContent(multipart);
3345: }
3346:
3347: return msg;
3348: }
3349:
3350: /**
3351: * Checks all of the settings if the caller requests a verify and a verify
3352: * was not performed yet and no verify is in progress. A verify is
3353: * performed on create because this handler may be at the end of a handler
3354: * chain and therefore may not see any log records until LogManager.reset()
3355: * is called and at that time all of the settings have been cleared.
3356: *
3357: * @param session the current session or null.
3358: * @since JavaMail 1.4.4
3359: */
3360: private void verifySettings(final Session session) {
3361: try {
3362: if (session != null) {
3363: final Properties props = session.getProperties();
3364: final Object check = props.put("verify", "");
3365: if (check instanceof String) {
3366: String value = (String) check;
3367: //Perform the verify if needed.
3368: if (hasValue(value)) {
3369: verifySettings0(session, value);
3370: }
3371: } else {
3372: if (check != null) { //Pass some invalid string.
3373: verifySettings0(session, check.getClass().toString());
3374: }
3375: }
3376: }
3377: } catch (final LinkageError JDK8152515) {
3378: reportLinkageError(JDK8152515, ErrorManager.OPEN_FAILURE);
3379: }
3380: }
3381:
3382: /**
3383: * Checks all of the settings using the given setting.
3384: * This triggers the LogManagerProperties to copy all of the mail
3385: * settings without explictly knowing them. Once all of the properties
3386: * are copied this handler can handle LogManager.reset clearing all of the
3387: * properties. It is expected that this method is, at most, only called
3388: * once per session.
3389: *
3390: * @param session the current session.
3391: * @param verify the type of verify to perform.
3392: * @since JavaMail 1.4.4
3393: */
3394: private void verifySettings0(Session session, String verify) {
3395: assert verify != null : (String) null;
3396: if (!"local".equals(verify) && !"remote".equals(verify)
3397: && !"limited".equals(verify) && !"resolve".equals(verify)
3398: && !"login".equals(verify)) {
3399: reportError("Verify must be 'limited', local', "
3400: + "'resolve', 'login', or 'remote'.",
3401: new IllegalArgumentException(verify),
3402: ErrorManager.OPEN_FAILURE);
3403: return;
3404: }
3405:
3406: final MimeMessage abort = new MimeMessage(session);
3407: final String msg;
3408: if (!"limited".equals(verify)) {
3409: msg = "Local address is "
3410: + InternetAddress.getLocalAddress(session) + '.';
3411:
3412: try { //Verify subclass or declared mime charset.
3413: Charset.forName(getEncodingName());
3414: } catch (final RuntimeException RE) {
3415: UnsupportedEncodingException UEE =
3416: new UnsupportedEncodingException(RE.toString());
3417: UEE.initCause(RE);
3418: reportError(msg, UEE, ErrorManager.FORMAT_FAILURE);
3419: }
3420: } else {
3421: msg = "Skipping local address check.";
3422: }
3423:
3424: //Perform all of the copy actions first.
3425: String[] atn;
3426: synchronized (this) { //Create the subject.
3427: appendSubject(abort, head(subjectFormatter));
3428: appendSubject(abort, tail(subjectFormatter, ""));
3429: atn = new String[attachmentNames.length];
3430: for (int i = 0; i < atn.length; ++i) {
3431: atn[i] = head(attachmentNames[i]);
3432: if (atn[i].length() == 0) {
3433: atn[i] = tail(attachmentNames[i], "");
3434: } else {
3435: atn[i] = atn[i].concat(tail(attachmentNames[i], ""));
3436: }
3437: }
3438: }
3439:
3440: setIncompleteCopy(abort); //Original body part is never added.
3441: envelopeFor(abort, true);
3442: saveChangesNoContent(abort, msg);
3443: try {
3444: //Ensure transport provider is installed.
3445: Address[] all = abort.getAllRecipients();
3446: if (all == null) { //Don't pass null to sendMessage.
3447: all = new InternetAddress[0];
3448: }
3449: Transport t;
3450: try {
3451: final Address[] any = all.length != 0 ? all : abort.getFrom();
3452: if (any != null && any.length != 0) {
3453: t = session.getTransport(any[0]);
3454: session.getProperty("mail.transport.protocol"); //Force copy
3455: } else {
3456: MessagingException me = new MessagingException(
3457: "No recipient or from address.");
3458: reportError(msg, me, ErrorManager.OPEN_FAILURE);
3459: throw me;
3460: }
3461: } catch (final MessagingException protocol) {
3462: //Switching the CCL emulates the current send behavior.
3463: Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3464: try {
3465: t = session.getTransport();
3466: } catch (final MessagingException fail) {
3467: throw attach(protocol, fail);
3468: } finally {
3469: getAndSetContextClassLoader(ccl);
3470: }
3471: }
3472:
3473: String local = null;
3474: if ("remote".equals(verify) || "login".equals(verify)) {
3475: MessagingException closed = null;
3476: t.connect();
3477: try {
3478: try {
3479: //Capture localhost while connection is open.
3480: local = getLocalHost(t);
3481:
3482: //A message without content will fail at message writeTo
3483: //when sendMessage is called. This allows the handler
3484: //to capture all mail properties set in the LogManager.
3485: if ("remote".equals(verify)) {
3486: t.sendMessage(abort, all);
3487: }
3488: } finally {
3489: try {
3490: t.close();
3491: } catch (final MessagingException ME) {
3492: closed = ME;
3493: }
3494: }
3495: //Close the transport before reporting errors.
3496: if ("remote".equals(verify)) {
3497: reportUnexpectedSend(abort, verify, null);
3498: } else {
3499: final String protocol = t.getURLName().getProtocol();
3500: verifyProperties(session, protocol);
3501: }
3502: } catch (final SendFailedException sfe) {
3503: Address[] recip = sfe.getInvalidAddresses();
3504: if (recip != null && recip.length != 0) {
3505: setErrorContent(abort, verify, sfe);
3506: reportError(abort, sfe, ErrorManager.OPEN_FAILURE);
3507: }
3508:
3509: recip = sfe.getValidSentAddresses();
3510: if (recip != null && recip.length != 0) {
3511: reportUnexpectedSend(abort, verify, sfe);
3512: }
3513: } catch (final MessagingException ME) {
3514: if (!isMissingContent(abort, ME)) {
3515: setErrorContent(abort, verify, ME);
3516: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3517: }
3518: }
3519:
3520: if (closed != null) {
3521: setErrorContent(abort, verify, closed);
3522: reportError(abort, closed, ErrorManager.CLOSE_FAILURE);
3523: }
3524: } else {
3525: //Force a property copy, JDK-7092981.
3526: final String protocol = t.getURLName().getProtocol();
3527: verifyProperties(session, protocol);
3528: String mailHost = session.getProperty("mail."
3529: + protocol + ".host");
3530: if (isEmpty(mailHost)) {
3531: mailHost = session.getProperty("mail.host");
3532: } else {
3533: session.getProperty("mail.host");
3534: }
3535:
3536: local = session.getProperty("mail." + protocol + ".localhost");
3537: if (isEmpty(local)) {
3538: local = session.getProperty("mail."
3539: + protocol + ".localaddress");
3540: } else {
3541: session.getProperty("mail." + protocol + ".localaddress");
3542: }
3543:
3544: if ("resolve".equals(verify)) {
3545: try { //Resolve the remote host name.
3546: String transportHost = t.getURLName().getHost();
3547: if (!isEmpty(transportHost)) {
3548: verifyHost(transportHost);
3549: if (!transportHost.equalsIgnoreCase(mailHost)) {
3550: verifyHost(mailHost);
3551: }
3552: } else {
3553: verifyHost(mailHost);
3554: }
3555: } catch (final RuntimeException | IOException IOE) {
3556: MessagingException ME =
3557: new MessagingException(msg, IOE);
3558: setErrorContent(abort, verify, ME);
3559: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3560: }
3561: }
3562: }
3563:
3564: if (!"limited".equals(verify)) {
3565: try { //Verify host name and hit the host name cache.
3566: if (!"remote".equals(verify) && !"login".equals(verify)) {
3567: local = getLocalHost(t);
3568: }
3569: verifyHost(local);
3570: } catch (final RuntimeException | IOException IOE) {
3571: MessagingException ME = new MessagingException(msg, IOE);
3572: setErrorContent(abort, verify, ME);
3573: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3574: }
3575:
3576: try { //Verify that the DataHandler can be loaded.
3577: Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3578: try {
3579: //Always load the multipart classes.
3580: MimeMultipart multipart = new MimeMultipart();
3581: MimeBodyPart[] ambp = new MimeBodyPart[atn.length];
3582: final MimeBodyPart body;
3583: final String bodyContentType;
3584: synchronized (this) {
3585: bodyContentType = contentTypeOf(getFormatter());
3586: body = createBodyPart();
3587: for (int i = 0; i < atn.length; ++i) {
3588: ambp[i] = createBodyPart(i);
3589: ambp[i].setFileName(atn[i]);
3590: //Convert names to mime type under lock.
3591: atn[i] = getContentType(atn[i]);
3592: }
3593: }
3594:
3595: body.setDescription(verify);
3596: setContent(body, "", bodyContentType);
3597: multipart.addBodyPart(body);
3598: for (int i = 0; i < ambp.length; ++i) {
3599: ambp[i].setDescription(verify);
3600: setContent(ambp[i], "", atn[i]);
3601: }
3602:
3603: abort.setContent(multipart);
3604: abort.saveChanges();
3605: abort.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
3606: } finally {
3607: getAndSetContextClassLoader(ccl);
3608: }
3609: } catch (final IOException IOE) {
3610: MessagingException ME = new MessagingException(msg, IOE);
3611: setErrorContent(abort, verify, ME);
3612: reportError(abort, ME, ErrorManager.FORMAT_FAILURE);
3613: }
3614: }
3615:
3616: //Verify all recipients.
3617: if (all.length != 0) {
3618: verifyAddresses(all);
3619: } else {
3620: throw new MessagingException("No recipient addresses.");
3621: }
3622:
3623: //Verify from and sender addresses.
3624: Address[] from = abort.getFrom();
3625: Address sender = abort.getSender();
3626: if (sender instanceof InternetAddress) {
3627: ((InternetAddress) sender).validate();
3628: }
3629:
3630: //If from address is declared then check sender.
3631: if (abort.getHeader("From", ",") != null && from.length != 0) {
3632: verifyAddresses(from);
3633: for (int i = 0; i < from.length; ++i) {
3634: if (from[i].equals(sender)) {
3635: MessagingException ME = new MessagingException(
3636: "Sender address '" + sender
3637: + "' equals from address.");
3638: throw new MessagingException(msg, ME);
3639: }
3640: }
3641: } else {
3642: if (sender == null) {
3643: MessagingException ME = new MessagingException(
3644: "No from or sender address.");
3645: throw new MessagingException(msg, ME);
3646: }
3647: }
3648:
3649: //Verify reply-to addresses.
3650: verifyAddresses(abort.getReplyTo());
3651: } catch (final Exception ME) {
3652: setErrorContent(abort, verify, ME);
3653: reportError(abort, ME, ErrorManager.OPEN_FAILURE);
3654: }
3655: }
3656:
3657: /**
3658: * Handles all exceptions thrown when save changes is called on a message
3659: * that doesn't have any content.
3660: *
3661: * @param abort the message requiring save changes.
3662: * @param msg the error description.
3663: * @since JavaMail 1.6.0
3664: */
3665: private void saveChangesNoContent(final Message abort, final String msg) {
3666: if (abort != null) {
3667: try {
3668: try {
3669: abort.saveChanges();
3670: } catch (final NullPointerException xferEncoding) {
3671: //Workaround GNU JavaMail bug in MimeUtility.getEncoding
3672: //when the mime message has no content.
3673: try {
3674: String cte = "Content-Transfer-Encoding";
3675: if (abort.getHeader(cte) == null) {
3676: abort.setHeader(cte, EncoderTypes.BASE_64.getEncoder());
3677: abort.saveChanges();
3678: } else {
3679: throw xferEncoding;
3680: }
3681: } catch (RuntimeException | MessagingException e) {
3682: if (e != xferEncoding) {
3683: e.addSuppressed(xferEncoding);
3684: }
3685: throw e;
3686: }
3687: }
3688: } catch (RuntimeException | MessagingException ME) {
3689: reportError(msg, ME, ErrorManager.FORMAT_FAILURE);
3690: }
3691: }
3692: }
3693:
3694: /**
3695: * Cache common session properties into the LogManagerProperties. This is
3696: * a workaround for JDK-7092981.
3697: *
3698: * @param session the session.
3699: * @param protocol the mail protocol.
3700: * @throws NullPointerException if session is null.
3701: * @since JavaMail 1.6.0
3702: */
3703: private static void verifyProperties(Session session, String protocol) {
3704: session.getProperty("mail.from");
3705: session.getProperty("mail." + protocol + ".from");
3706: session.getProperty("mail.dsn.ret");
3707: session.getProperty("mail." + protocol + ".dsn.ret");
3708: session.getProperty("mail.dsn.notify");
3709: session.getProperty("mail." + protocol + ".dsn.notify");
3710: session.getProperty("mail." + protocol + ".port");
3711: session.getProperty("mail.user");
3712: session.getProperty("mail." + protocol + ".user");
3713: session.getProperty("mail." + protocol + ".localport");
3714: }
3715:
3716: /**
3717: * Perform a lookup of the host address or FQDN.
3718: *
3719: * @param host the host or null.
3720: * @return the address.
3721: * @throws IOException if the host name is not valid.
3722: * @throws SecurityException if security manager is present and doesn't
3723: * allow access to check connect permission.
3724: * @since JavaMail 1.5.0
3725: */
3726: private static InetAddress verifyHost(String host) throws IOException {
3727: InetAddress a;
3728: if (isEmpty(host)) {
3729: a = InetAddress.getLocalHost();
3730: } else {
3731: a = InetAddress.getByName(host);
3732: }
3733: if (a.getCanonicalHostName().length() == 0) {
3734: throw new UnknownHostException();
3735: }
3736: return a;
3737: }
3738:
3739: /**
3740: * Calls validate for every address given.
3741: * If the addresses given are null, empty or not an InternetAddress then
3742: * the check is skipped.
3743: *
3744: * @param all any address array, null or empty.
3745: * @throws AddressException if there is a problem.
3746: * @since JavaMail 1.4.5
3747: */
3748: private static void verifyAddresses(Address[] all) throws AddressException {
3749: if (all != null) {
3750: for (int i = 0; i < all.length; ++i) {
3751: final Address a = all[i];
3752: if (a instanceof InternetAddress) {
3753: ((InternetAddress) a).validate();
3754: }
3755: }
3756: }
3757: }
3758:
3759: /**
3760: * Reports that an empty content message was sent and should not have been.
3761: *
3762: * @param msg the MimeMessage.
3763: * @param verify the verify enum.
3764: * @param cause the exception that caused the problem or null.
3765: * @since JavaMail 1.4.5
3766: */
3767: private void reportUnexpectedSend(MimeMessage msg, String verify, Exception cause) {
3768: final MessagingException write = new MessagingException(
3769: "An empty message was sent.", cause);
3770: setErrorContent(msg, verify, write);
3771: reportError(msg, write, ErrorManager.OPEN_FAILURE);
3772: }
3773:
3774: /**
3775: * Creates and sets the message content from the given Throwable.
3776: * When verify fails, this method fixes the 'abort' message so that any
3777: * created envelope data can be used in the error manager.
3778: *
3779: * @param msg the message with or without content.
3780: * @param verify the verify enum.
3781: * @param t the throwable or null.
3782: * @since JavaMail 1.4.5
3783: */
3784: private void setErrorContent(MimeMessage msg, String verify, Throwable t) {
3785: try { //Add content so toRawString doesn't fail.
3786: final MimeBodyPart body;
3787: final String subjectType;
3788: final String msgDesc;
3789: synchronized (this) {
3790: body = createBodyPart();
3791: msgDesc = descriptionFrom(comparator, pushLevel, pushFilter);
3792: subjectType = getClassId(subjectFormatter);
3793: }
3794:
3795: body.setDescription("Formatted using "
3796: + (t == null ? Throwable.class.getName()
3797: : t.getClass().getName()) + ", filtered with "
3798: + verify + ", and named by "
3799: + subjectType + '.');
3800: setContent(body, toMsgString(t), "text/plain");
3801: final MimeMultipart multipart = new MimeMultipart();
3802: multipart.addBodyPart(body);
3803: msg.setContent(multipart);
3804: msg.setDescription(msgDesc);
3805: setAcceptLang(msg);
3806: msg.saveChanges();
3807: } catch (MessagingException | RuntimeException ME) {
3808: reportError("Unable to create body.", ME, ErrorManager.OPEN_FAILURE);
3809: }
3810: }
3811:
3812: /**
3813: * Used to update the cached session object based on changes in
3814: * mail properties or authenticator.
3815: *
3816: * @return the current session or null if no verify is required.
3817: */
3818: private Session updateSession() {
3819: assert Thread.holdsLock(this);
3820: final Session settings;
3821: if (mailProps.getProperty("verify") != null) {
3822: settings = initSession();
3823: assert settings == session : session;
3824: } else {
3825: session = null; //Remove old session.
3826: settings = null;
3827: }
3828: return settings;
3829: }
3830:
3831: /**
3832: * Creates a session using a proxy properties object.
3833: *
3834: * @return the session that was created and assigned.
3835: */
3836: private Session initSession() {
3837: assert Thread.holdsLock(this);
3838: final String p = getClass().getName();
3839: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3840: try {
3841: LogManagerProperties proxy = new LogManagerProperties(mailProps, p);
3842: session = Session.getInstance(proxy, auth);
3843: } finally {
3844: getAndSetContextClassLoader(ccl);
3845: }
3846: return session;
3847: }
3848:
3849: /**
3850: * Creates all of the envelope information for a message.
3851: * This method is safe to call outside of a lock because the message
3852: * provides the safe snapshot of the mail properties.
3853: *
3854: * @param msg the Message to write the envelope information.
3855: * @param priority true for high priority.
3856: */
3857: private void envelopeFor(Message msg, boolean priority) {
3858: setAcceptLang(msg);
3859: setFrom(msg);
3860: if (!setRecipient(msg, "mail.to", Message.RecipientType.TO)) {
3861: setDefaultRecipient(msg, Message.RecipientType.TO);
3862: }
3863: setRecipient(msg, "mail.cc", Message.RecipientType.CC);
3864: setRecipient(msg, "mail.bcc", Message.RecipientType.BCC);
3865: setReplyTo(msg);
3866: setSender(msg);
3867: setMailer(msg);
3868: setAutoSubmitted(msg);
3869: if (priority) {
3870: setPriority(msg);
3871: }
3872:
3873: try {
3874: msg.setSentDate(new java.util.Date());
3875: } catch (final MessagingException ME) {
3876: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
3877: }
3878: }
3879:
3880: private MimeMultipart createMultipart() throws MessagingException {
3881: assert Thread.holdsLock(this);
3882: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3883: try {
3884: return new MimeMultipart();
3885: } finally {
3886: getAndSetContextClassLoader(ccl);
3887: }
3888: }
3889:
3890: /**
3891: * Factory to create the in-line body part.
3892: *
3893: * @return a body part with default headers set.
3894: * @throws MessagingException if there is a problem.
3895: */
3896: private MimeBodyPart createBodyPart() throws MessagingException {
3897: assert Thread.holdsLock(this);
3898: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3899: try {
3900: final MimeBodyPart part = new MimeBodyPart();
3901: part.setDisposition(Part.INLINE);
3902: part.setDescription(descriptionFrom(getFormatter(),
3903: getFilter(), subjectFormatter));
3904: setAcceptLang(part);
3905: return part;
3906: } finally {
3907: getAndSetContextClassLoader(ccl);
3908: }
3909: }
3910:
3911: /**
3912: * Factory to create the attachment body part.
3913: *
3914: * @param index the attachment index.
3915: * @return a body part with default headers set.
3916: * @throws MessagingException if there is a problem.
3917: * @throws IndexOutOfBoundsException if the given index is not an valid
3918: * attachment index.
3919: */
3920: private MimeBodyPart createBodyPart(int index) throws MessagingException {
3921: assert Thread.holdsLock(this);
3922: final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
3923: try {
3924: final MimeBodyPart part = new MimeBodyPart();
3925: part.setDisposition(Part.ATTACHMENT);
3926: part.setDescription(descriptionFrom(
3927: attachmentFormatters[index],
3928: attachmentFilters[index],
3929: attachmentNames[index]));
3930: setAcceptLang(part);
3931: return part;
3932: } finally {
3933: getAndSetContextClassLoader(ccl);
3934: }
3935: }
3936:
3937: /**
3938: * Gets the description for the MimeMessage itself.
3939: * The push level and filter are included because they play a role in
3940: * formatting of a message when triggered or not triggered.
3941: *
3942: * @param c the comparator.
3943: * @param l the pushLevel.
3944: * @param f the pushFilter
3945: * @return the description.
3946: * @throws NullPointerException if level is null.
3947: * @since JavaMail 1.4.5
3948: */
3949: private String descriptionFrom(Comparator<?> c, Level l, Filter f) {
3950: return "Sorted using " + (c == null ? "no comparator"
3951: : c.getClass().getName()) + ", pushed when " + l.getName()
3952: + ", and " + (f == null ? "no push filter"
3953: : f.getClass().getName()) + '.';
3954: }
3955:
3956: /**
3957: * Creates a description for a body part.
3958: *
3959: * @param f the content formatter.
3960: * @param filter the content filter.
3961: * @param name the naming formatter.
3962: * @return the description for the body part.
3963: */
3964: private String descriptionFrom(Formatter f, Filter filter, Formatter name) {
3965: return "Formatted using " + getClassId(f)
3966: + ", filtered with " + (filter == null ? "no filter"
3967: : filter.getClass().getName()) + ", and named by "
3968: + getClassId(name) + '.';
3969: }
3970:
3971: /**
3972: * Gets a class name represents the behavior of the formatter.
3973: * The class name may not be assignable to a Formatter.
3974: *
3975: * @param f the formatter.
3976: * @return a class name that represents the given formatter.
3977: * @throws NullPointerException if the parameter is null.
3978: * @since JavaMail 1.4.5
3979: */
3980: private String getClassId(final Formatter f) {
3981: if (f instanceof TailNameFormatter) {
3982: return String.class.getName(); //Literal string.
3983: } else {
3984: return f.getClass().getName();
3985: }
3986: }
3987:
3988: /**
3989: * Ensure that a formatter creates a valid string for a part name.
3990: *
3991: * @param f the formatter.
3992: * @return the to string value or the class name.
3993: */
3994: private String toString(final Formatter f) {
3995: //Should never be null but, guard against formatter bugs.
3996: final String name = f.toString();
3997: if (!isEmpty(name)) {
3998: return name;
3999: } else {
4000: return getClassId(f);
4001: }
4002: }
4003:
4004: /**
4005: * Constructs a file name from a formatter. This method is called often
4006: * but, rarely does any work.
4007: *
4008: * @param part to append to.
4009: * @param chunk non null string to append.
4010: */
4011: private void appendFileName(final Part part, final String chunk) {
4012: if (chunk != null) {
4013: if (chunk.length() > 0) {
4014: appendFileName0(part, chunk);
4015: }
4016: } else {
4017: reportNullError(ErrorManager.FORMAT_FAILURE);
4018: }
4019: }
4020:
4021: /**
4022: * It is assumed that file names are short and that in most cases
4023: * getTail will be the only method that will produce a result.
4024: *
4025: * @param part to append to.
4026: * @param chunk non null string to append.
4027: */
4028: private void appendFileName0(final Part part, String chunk) {
4029: try {
4030: //Remove all control character groups.
4031: chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
4032: final String old = part.getFileName();
4033: part.setFileName(old != null ? old.concat(chunk) : chunk);
4034: } catch (final MessagingException ME) {
4035: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4036: }
4037: }
4038:
4039: /**
4040: * Constructs a subject line from a formatter.
4041: *
4042: * @param msg to append to.
4043: * @param chunk non null string to append.
4044: */
4045: private void appendSubject(final Message msg, final String chunk) {
4046: if (chunk != null) {
4047: if (chunk.length() > 0) {
4048: appendSubject0(msg, chunk);
4049: }
4050: } else {
4051: reportNullError(ErrorManager.FORMAT_FAILURE);
4052: }
4053: }
4054:
4055: /**
4056: * It is assumed that subject lines are short and that in most cases
4057: * getTail will be the only method that will produce a result.
4058: *
4059: * @param msg to append to.
4060: * @param chunk non null string to append.
4061: */
4062: private void appendSubject0(final Message msg, String chunk) {
4063: try {
4064: //Remove all control character groups.
4065: chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
4066: final String charset = getEncodingName();
4067: final String old = msg.getSubject();
4068: assert msg instanceof MimeMessage : msg;
4069: ((MimeMessage) msg).setSubject(old != null ? old.concat(chunk)
4070: : chunk, MimeUtility.mimeCharset(charset));
4071: } catch (final MessagingException ME) {
4072: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4073: }
4074: }
4075:
4076: /**
4077: * Gets the locale for the given log record from the resource bundle.
4078: * If the resource bundle is using the root locale then the default locale
4079: * is returned.
4080: *
4081: * @param r the log record.
4082: * @return null if not localized otherwise, the locale of the record.
4083: * @since JavaMail 1.4.5
4084: */
4085: private Locale localeFor(final LogRecord r) {
4086: Locale l;
4087: final ResourceBundle rb = r.getResourceBundle();
4088: if (rb != null) {
4089: l = rb.getLocale();
4090: if (l == null || isEmpty(l.getLanguage())) {
4091: //The language of the fallback bundle (root) is unknown.
4092: //1. Use default locale. Should only be wrong if the app is
4093: // used with a langauge that was unintended. (unlikely)
4094: //2. Mark it as not localized (force null, info loss).
4095: //3. Use the bundle name (encoded) as an experimental language.
4096: l = Locale.getDefault();
4097: }
4098: } else {
4099: l = null;
4100: }
4101: return l;
4102: }
4103:
4104: /**
4105: * Appends the content language to the given mime part.
4106: * The language tag is only appended if the given language has not been
4107: * specified. This method is only used when we have LogRecords that are
4108: * localized with an assigned resource bundle.
4109: *
4110: * @param p the mime part.
4111: * @param l the locale to append.
4112: * @throws NullPointerException if any argument is null.
4113: * @since JavaMail 1.4.5
4114: */
4115: private void appendContentLang(final MimePart p, final Locale l) {
4116: try {
4117: String lang = LogManagerProperties.toLanguageTag(l);
4118: if (lang.length() != 0) {
4119: String header = p.getHeader("Content-Language", null);
4120: if (isEmpty(header)) {
4121: p.setHeader("Content-Language", lang);
4122: } else if (!header.equalsIgnoreCase(lang)) {
4123: lang = ",".concat(lang);
4124: int idx = 0;
4125: while ((idx = header.indexOf(lang, idx)) > -1) {
4126: idx += lang.length();
4127: if (idx == header.length()
4128: || header.charAt(idx) == ',') {
4129: break;
4130: }
4131: }
4132:
4133: if (idx < 0) {
4134: int len = header.lastIndexOf("\r\n\t");
4135: if (len < 0) { //If not folded.
4136: len = (18 + 2) + header.length();
4137: } else {
4138: len = (header.length() - len) + 8;
4139: }
4140:
4141: //Perform folding of header if needed.
4142: if ((len + lang.length()) > 76) {
4143: header = header.concat("\r\n\t".concat(lang));
4144: } else {
4145: header = header.concat(lang);
4146: }
4147: p.setHeader("Content-Language", header);
4148: }
4149: }
4150: }
4151: } catch (final MessagingException ME) {
4152: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4153: }
4154: }
4155:
4156: /**
4157: * Sets the accept language to the default locale of the JVM.
4158: * If the locale is the root locale the header is not added.
4159: *
4160: * @param p the part to set.
4161: * @since JavaMail 1.4.5
4162: */
4163: private void setAcceptLang(final Part p) {
4164: try {
4165: final String lang = LogManagerProperties
4166: .toLanguageTag(Locale.getDefault());
4167: if (lang.length() != 0) {
4168: p.setHeader("Accept-Language", lang);
4169: }
4170: } catch (final MessagingException ME) {
4171: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4172: }
4173: }
4174:
4175: /**
4176: * Used when a log record was loggable prior to being inserted
4177: * into the buffer but at the time of formatting was no longer loggable.
4178: * Filters were changed after publish but prior to a push or a bug in the
4179: * body filter or one of the attachment filters.
4180: *
4181: * @param record that was not formatted.
4182: * @since JavaMail 1.4.5
4183: */
4184: private void reportFilterError(final LogRecord record) {
4185: assert Thread.holdsLock(this);
4186: final Formatter f = createSimpleFormatter();
4187: final String msg = "Log record " + record.getSequenceNumber()
4188: + " was filtered from all message parts. "
4189: + head(f) + format(f, record) + tail(f, "");
4190: final String txt = getFilter() + ", "
4191: + Arrays.asList(readOnlyAttachmentFilters());
4192: reportError(msg, new IllegalArgumentException(txt),
4193: ErrorManager.FORMAT_FAILURE);
4194: }
4195:
4196: /**
4197: * Reports symmetric contract violations an equals implementation.
4198: *
4199: * @param o the test object must be non null.
4200: * @param found the possible intern, must be non null.
4201: * @throws NullPointerException if any argument is null.
4202: * @since JavaMail 1.5.0
4203: */
4204: private void reportNonSymmetric(final Object o, final Object found) {
4205: reportError("Non symmetric equals implementation."
4206: , new IllegalArgumentException(o.getClass().getName()
4207: + " is not equal to " + found.getClass().getName())
4208: , ErrorManager.OPEN_FAILURE);
4209: }
4210:
4211: /**
4212: * Reports equals implementations that do not discriminate between objects
4213: * of different types or subclass types.
4214: *
4215: * @param o the test object must be non null.
4216: * @param found the possible intern, must be non null.
4217: * @throws NullPointerException if any argument is null.
4218: * @since JavaMail 1.5.0
4219: */
4220: private void reportNonDiscriminating(final Object o, final Object found) {
4221: reportError("Non discriminating equals implementation."
4222: , new IllegalArgumentException(o.getClass().getName()
4223: + " should not be equal to " + found.getClass().getName())
4224: , ErrorManager.OPEN_FAILURE);
4225: }
4226:
4227: /**
4228: * Used to outline the bytes to report a null pointer exception.
4229: * See BUD ID 6533165.
4230: *
4231: * @param code the ErrorManager code.
4232: */
4233: private void reportNullError(final int code) {
4234: reportError("null", new NullPointerException(), code);
4235: }
4236:
4237: /**
4238: * Creates the head or reports a formatting error.
4239: *
4240: * @param f the formatter.
4241: * @return the head string or an empty string.
4242: */
4243: private String head(final Formatter f) {
4244: try {
4245: return f.getHead(this);
4246: } catch (final RuntimeException RE) {
4247: reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
4248: return "";
4249: }
4250: }
4251:
4252: /**
4253: * Creates the formatted log record or reports a formatting error.
4254: *
4255: * @param f the formatter.
4256: * @param r the log record.
4257: * @return the formatted string or an empty string.
4258: */
4259: private String format(final Formatter f, final LogRecord r) {
4260: try {
4261: return f.format(r);
4262: } catch (final RuntimeException RE) {
4263: reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
4264: return "";
4265: }
4266: }
4267:
4268: /**
4269: * Creates the tail or reports a formatting error.
4270: *
4271: * @param f the formatter.
4272: * @param def the default string to use when there is an error.
4273: * @return the tail string or the given default string.
4274: */
4275: private String tail(final Formatter f, final String def) {
4276: try {
4277: return f.getTail(this);
4278: } catch (final RuntimeException RE) {
4279: reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
4280: return def;
4281: }
4282: }
4283:
4284: /**
4285: * Sets the x-mailer header.
4286: *
4287: * @param msg the target message.
4288: */
4289: private void setMailer(final Message msg) {
4290: try {
4291: final Class<?> mail = MailHandler.class;
4292: final Class<?> k = getClass();
4293: String value;
4294: if (k == mail) {
4295: value = mail.getName();
4296: } else {
4297: try {
4298: value = MimeUtility.encodeText(k.getName());
4299: } catch (final UnsupportedEncodingException E) {
4300: reportError(E.getMessage(), E, ErrorManager.FORMAT_FAILURE);
4301: value = k.getName().replaceAll("[^\\x00-\\x7F]", "\uu001A");
4302: }
4303: value = MimeUtility.fold(10, mail.getName() + " using the "
4304: + value + " extension.");
4305: }
4306: msg.setHeader("X-Mailer", value);
4307: } catch (final MessagingException ME) {
4308: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4309: }
4310: }
4311:
4312: /**
4313: * Sets the priority and importance headers.
4314: *
4315: * @param msg the target message.
4316: */
4317: private void setPriority(final Message msg) {
4318: try {
4319: msg.setHeader("Importance", "High");
4320: msg.setHeader("Priority", "urgent");
4321: msg.setHeader("X-Priority", "2"); //High
4322: } catch (final MessagingException ME) {
4323: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4324: }
4325: }
4326:
4327: /**
4328: * Used to signal that body parts are missing from a message. Also used
4329: * when LogRecords were passed to an attachment formatter but the formatter
4330: * produced no output, which is allowed. Used during a verify because all
4331: * parts are omitted, none of the content formatters are used. This is
4332: * not used when a filter prevents LogRecords from being formatted.
4333: * This header is defined in RFC 2156 and RFC 4021.
4334: *
4335: * @param msg the message.
4336: * @since JavaMail 1.4.5
4337: */
4338: private void setIncompleteCopy(final Message msg) {
4339: try {
4340: msg.setHeader("Incomplete-Copy", "");
4341: } catch (final MessagingException ME) {
4342: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4343: }
4344: }
4345:
4346: /**
4347: * Signals that this message was generated by automatic process.
4348: * This header is defined in RFC 3834 section 5.
4349: *
4350: * @param msg the message.
4351: * @since JavaMail 1.4.6
4352: */
4353: private void setAutoSubmitted(final Message msg) {
4354: if (allowRestrictedHeaders()) {
4355: try { //RFC 3834 (5.2)
4356: msg.setHeader("auto-submitted", "auto-generated");
4357: } catch (final MessagingException ME) {
4358: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4359: }
4360: }
4361: }
4362:
4363: /**
4364: * Sets from address header.
4365: *
4366: * @param msg the target message.
4367: */
4368: private void setFrom(final Message msg) {
4369: final String from = getSession(msg).getProperty("mail.from");
4370: if (from != null) {
4371: try {
4372: final Address[] address = InternetAddress.parse(from, false);
4373: if (address.length > 0) {
4374: if (address.length == 1) {
4375: msg.setFrom(address[0]);
4376: } else { //Greater than 1 address.
4377: msg.addFrom(address);
4378: }
4379: }
4380: //Can't place an else statement here because the 'from' is
4381: //not null which causes the local address computation
4382: //to fail. Assume the user wants to omit the from address
4383: //header.
4384: } catch (final MessagingException ME) {
4385: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4386: setDefaultFrom(msg);
4387: }
4388: } else {
4389: setDefaultFrom(msg);
4390: }
4391: }
4392:
4393: /**
4394: * Sets the from header to the local address.
4395: *
4396: * @param msg the target message.
4397: */
4398: private void setDefaultFrom(final Message msg) {
4399: try {
4400: msg.setFrom();
4401: } catch (final MessagingException ME) {
4402: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4403: }
4404: }
4405:
4406: /**
4407: * Computes the default to-address if none was specified. This can
4408: * fail if the local address can't be computed.
4409: *
4410: * @param msg the message
4411: * @param type the recipient type.
4412: * @since JavaMail 1.5.0
4413: */
4414: private void setDefaultRecipient(final Message msg,
4415: final Message.RecipientType type) {
4416: try {
4417: Address a = InternetAddress.getLocalAddress(getSession(msg));
4418: if (a != null) {
4419: msg.setRecipient(type, a);
4420: } else {
4421: final MimeMessage m = new MimeMessage(getSession(msg));
4422: m.setFrom(); //Should throw an exception with a cause.
4423: Address[] from = m.getFrom();
4424: if (from.length > 0) {
4425: msg.setRecipients(type, from);
4426: } else {
4427: throw new MessagingException("No local address.");
4428: }
4429: }
4430: } catch (MessagingException | RuntimeException ME) {
4431: reportError("Unable to compute a default recipient.",
4432: ME, ErrorManager.FORMAT_FAILURE);
4433: }
4434: }
4435:
4436: /**
4437: * Sets reply-to address header.
4438: *
4439: * @param msg the target message.
4440: */
4441: private void setReplyTo(final Message msg) {
4442: final String reply = getSession(msg).getProperty("mail.reply.to");
4443: if (!isEmpty(reply)) {
4444: try {
4445: final Address[] address = InternetAddress.parse(reply, false);
4446: if (address.length > 0) {
4447: msg.setReplyTo(address);
4448: }
4449: } catch (final MessagingException ME) {
4450: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4451: }
4452: }
4453: }
4454:
4455: /**
4456: * Sets sender address header.
4457: *
4458: * @param msg the target message.
4459: */
4460: private void setSender(final Message msg) {
4461: assert msg instanceof MimeMessage : msg;
4462: final String sender = getSession(msg).getProperty("mail.sender");
4463: if (!isEmpty(sender)) {
4464: try {
4465: final InternetAddress[] address =
4466: InternetAddress.parse(sender, false);
4467: if (address.length > 0) {
4468: ((MimeMessage) msg).setSender(address[0]);
4469: if (address.length > 1) {
4470: reportError("Ignoring other senders.",
4471: tooManyAddresses(address, 1),
4472: ErrorManager.FORMAT_FAILURE);
4473: }
4474: }
4475: } catch (final MessagingException ME) {
4476: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4477: }
4478: }
4479: }
4480:
4481: /**
4482: * A common factory used to create the too many addresses exception.
4483: *
4484: * @param address the addresses, never null.
4485: * @param offset the starting address to display.
4486: * @return the too many addresses exception.
4487: */
4488: private AddressException tooManyAddresses(Address[] address, int offset) {
4489: Object l = Arrays.asList(address).subList(offset, address.length);
4490: return new AddressException(l.toString());
4491: }
4492:
4493: /**
4494: * Sets the recipient for the given message.
4495: *
4496: * @param msg the message.
4497: * @param key the key to search in the session.
4498: * @param type the recipient type.
4499: * @return true if the key was contained in the session.
4500: */
4501: private boolean setRecipient(final Message msg,
4502: final String key, final Message.RecipientType type) {
4503: boolean containsKey;
4504: final String value = getSession(msg).getProperty(key);
4505: containsKey = value != null;
4506: if (!isEmpty(value)) {
4507: try {
4508: final Address[] address = InternetAddress.parse(value, false);
4509: if (address.length > 0) {
4510: msg.setRecipients(type, address);
4511: }
4512: } catch (final MessagingException ME) {
4513: reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
4514: }
4515: }
4516: return containsKey;
4517: }
4518:
4519: /**
4520: * Converts an email message to a raw string. This raw string
4521: * is passed to the error manager to allow custom error managers
4522: * to recreate the original MimeMessage object.
4523: *
4524: * @param msg a Message object.
4525: * @return the raw string or null if msg was null.
4526: * @throws MessagingException if there was a problem with the message.
4527: * @throws IOException if there was a problem.
4528: */
4529: private String toRawString(final Message msg) throws MessagingException, IOException {
4530: if (msg != null) {
4531: Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
4532: try { //JDK-8025251
4533: int nbytes = Math.max(msg.getSize() + MIN_HEADER_SIZE, MIN_HEADER_SIZE);
4534: ByteArrayOutputStream out = new ByteArrayOutputStream(nbytes);
4535: msg.writeTo(out); //Headers can be UTF-8 or US-ASCII.
4536: return out.toString("UTF-8");
4537: } finally {
4538: getAndSetContextClassLoader(ccl);
4539: }
4540: } else { //Must match this.reportError behavior, see push method.
4541: return null; //Null is the safe choice.
4542: }
4543: }
4544:
4545: /**
4546: * Converts a throwable to a message string.
4547: *
4548: * @param t any throwable or null.
4549: * @return the throwable with a stack trace or the literal null.
4550: */
4551: private String toMsgString(final Throwable t) {
4552: if (t == null) {
4553: return "null";
4554: }
4555:
4556: final String charset = getEncodingName();
4557: try {
4558: final ByteArrayOutputStream out =
4559: new ByteArrayOutputStream(MIN_HEADER_SIZE);
4560:
4561: //Create an output stream writer so streams are not double buffered.
4562: try (OutputStreamWriter ows = new OutputStreamWriter(out, charset);
4563: PrintWriter pw = new PrintWriter(ows)) {
4564: pw.println(t.getMessage());
4565: t.printStackTrace(pw);
4566: pw.flush();
4567: } //Close OSW before generating string. JDK-6995537
4568: return out.toString(charset);
4569: } catch (final Exception badMimeCharset) {
4570: return t.toString() + ' ' + badMimeCharset.toString();
4571: }
4572: }
4573:
4574: /**
4575: * Replaces the current context class loader with our class loader.
4576: *
4577: * @param ccl null for boot class loader, a class loader, a class used to
4578: * get the class loader, or a source object to get the class loader.
4579: * @return null for the boot class loader, a class loader, or a marker
4580: * object to signal that no modification was required.
4581: * @since JavaMail 1.5.3
4582: */
4583: private Object getAndSetContextClassLoader(final Object ccl) {
4584: if (ccl != GetAndSetContext.NOT_MODIFIED) {
4585: try {
4586: final PrivilegedAction<?> pa;
4587: if (ccl instanceof PrivilegedAction) {
4588: pa = (PrivilegedAction<?>) ccl;
4589: } else {
4590: pa = new GetAndSetContext(ccl);
4591: }
4592: return AccessController.doPrivileged(pa);
4593: } catch (final SecurityException ignore) {
4594: }
4595: }
4596: return GetAndSetContext.NOT_MODIFIED;
4597: }
4598:
4599: /**
4600: * A factory used to create a common attachment mismatch type.
4601: *
4602: * @param msg the exception message.
4603: * @return a RuntimeException to represent the type of error.
4604: */
4605: private static RuntimeException attachmentMismatch(final String msg) {
4606: return new IndexOutOfBoundsException(msg);
4607: }
4608:
4609: /**
4610: * Try to attach a suppressed exception to a MessagingException in any order
4611: * that is possible.
4612: *
4613: * @param required the exception expected to see as a reported failure.
4614: * @param optional the suppressed exception.
4615: * @return either the required or the optional exception.
4616: */
4617: private static MessagingException attach(
4618: MessagingException required, Exception optional) {
4619: if (optional != null && !required.setNextException(optional)) {
4620: if (optional instanceof MessagingException) {
4621: final MessagingException head = (MessagingException) optional;
4622: if (head.setNextException(required)) {
4623: return head;
4624: }
4625: }
4626:
4627: if (optional != required) {
4628: required.addSuppressed(optional);
4629: }
4630: }
4631: return required;
4632: }
4633:
4634: /**
4635: * Gets the local host from the given service object.
4636: *
4637: * @param s the service to check.
4638: * @return the local host or null.
4639: * @since JavaMail 1.5.3
4640: */
4641: private String getLocalHost(final Service s) {
4642: try {
4643: return LogManagerProperties.getLocalHost(s);
4644: } catch (SecurityException | NoSuchMethodException
4645: | LinkageError ignore) {
4646: } catch (final Exception ex) {
4647: reportError(s.toString(), ex, ErrorManager.OPEN_FAILURE);
4648: }
4649: return null;
4650: }
4651:
4652: /**
4653: * Google App Engine doesn't support Message.getSession.
4654: *
4655: * @param msg the message.
4656: * @return the session from the given message.
4657: * @throws NullPointerException if the given message is null.
4658: * @since JavaMail 1.5.3
4659: */
4660: private Session getSession(final Message msg) {
4661: if (msg == null) {
4662: throw new NullPointerException();
4663: }
4664: return new MessageContext(msg).getSession();
4665: }
4666:
4667: /**
4668: * Determines if restricted headers are allowed in the current environment.
4669: *
4670: * @return true if restricted headers are allowed.
4671: * @since JavaMail 1.5.3
4672: */
4673: private boolean allowRestrictedHeaders() {
4674: //GAE will prevent delivery of email with forbidden headers.
4675: //Assume the environment is GAE if access to the LogManager is
4676: //forbidden.
4677: return LogManagerProperties.hasLogManager();
4678: }
4679:
4680: /**
4681: * Used for storing a password from the LogManager or literal string.
4682: *
4683: * @since JavaMail 1.4.6
4684: */
4685: private static final class DefaultAuthenticator extends Authenticator {
4686:
4687: /**
4688: * Creates an Authenticator for the given password. This method is used
4689: * so class verification of assignments in MailHandler doesn't require
4690: * loading this class which otherwise can occur when using the
4691: * constructor. Default access to avoid generating extra class files.
4692: *
4693: * @param pass the password.
4694: * @return an Authenticator for the password.
4695: * @since JavaMail 1.5.6
4696: */
4697: static Authenticator of(final String pass) {
4698: return new DefaultAuthenticator(pass);
4699: }
4700:
4701: /**
4702: * The password to use.
4703: */
4704: private final String pass;
4705:
4706: /**
4707: * Use the factory method instead of this constructor.
4708: *
4709: * @param pass the password.
4710: */
4711: private DefaultAuthenticator(final String pass) {
4712: assert pass != null;
4713: this.pass = pass;
4714: }
4715:
4716: @Override
4717: protected final PasswordAuthentication getPasswordAuthentication() {
4718: return new PasswordAuthentication(getDefaultUserName(), pass);
4719: }
4720: }
4721:
4722: /**
4723: * Performs a get and set of the context class loader with privileges
4724: * enabled.
4725: *
4726: * @since JavaMail 1.4.6
4727: */
4728: private static final class GetAndSetContext implements PrivilegedAction<Object> {
4729: /**
4730: * A marker object used to signal that the class loader was not
4731: * modified.
4732: */
4733: public static final Object NOT_MODIFIED = GetAndSetContext.class;
4734: /**
4735: * The source containing the class loader.
4736: */
4737: private final Object source;
4738:
4739: /**
4740: * Create the action.
4741: *
4742: * @param source null for boot class loader, a class loader, a class
4743: * used to get the class loader, or a source object to get the class
4744: * loader. Default access to avoid generating extra class files.
4745: */
4746: GetAndSetContext(final Object source) {
4747: this.source = source;
4748: }
4749:
4750: /**
4751: * Gets the class loader from the source and sets the CCL only if
4752: * the source and CCL are not the same.
4753: *
4754: * @return the replaced context class loader which can be null or
4755: * NOT_MODIFIED to indicate that nothing was modified.
4756: */
4757: @SuppressWarnings("override") //JDK-6954234
4758: public final Object run() {
4759: final Thread current = Thread.currentThread();
4760: final ClassLoader ccl = current.getContextClassLoader();
4761: final ClassLoader loader;
4762:• if (source == null) {
4763: loader = null; //boot class loader
4764:• } else if (source instanceof ClassLoader) {
4765: loader = (ClassLoader) source;
4766:• } else if (source instanceof Class) {
4767: loader = ((Class<?>) source).getClassLoader();
4768:• } else if (source instanceof Thread) {
4769: loader = ((Thread) source).getContextClassLoader();
4770: } else {
4771:• assert !(source instanceof Class) : source;
4772: loader = source.getClass().getClassLoader();
4773: }
4774:
4775:• if (ccl != loader) {
4776: current.setContextClassLoader(loader);
4777: return ccl;
4778: } else {
4779: return NOT_MODIFIED;
4780: }
4781: }
4782: }
4783:
4784: /**
4785: * Used for naming attachment file names and the main subject line.
4786: */
4787: private static final class TailNameFormatter extends Formatter {
4788:
4789: /**
4790: * Creates or gets a formatter from the given name. This method is used
4791: * so class verification of assignments in MailHandler doesn't require
4792: * loading this class which otherwise can occur when using the
4793: * constructor. Default access to avoid generating extra class files.
4794: *
4795: * @param name any not null string.
4796: * @return a formatter for that string.
4797: * @since JavaMail 1.5.6
4798: */
4799: static Formatter of(final String name) {
4800: return new TailNameFormatter(name);
4801: }
4802:
4803: /**
4804: * The value used as the output.
4805: */
4806: private final String name;
4807:
4808: /**
4809: * Use the factory method instead of this constructor.
4810: *
4811: * @param name any not null string.
4812: */
4813: private TailNameFormatter(final String name) {
4814: assert name != null;
4815: this.name = name;
4816: }
4817:
4818: @Override
4819: public final String format(LogRecord record) {
4820: return "";
4821: }
4822:
4823: @Override
4824: public final String getTail(Handler h) {
4825: return name;
4826: }
4827:
4828: /**
4829: * Equals method.
4830: *
4831: * @param o the other object.
4832: * @return true if equal
4833: * @since JavaMail 1.4.4
4834: */
4835: @Override
4836: public final boolean equals(Object o) {
4837: if (o instanceof TailNameFormatter) {
4838: return name.equals(((TailNameFormatter) o).name);
4839: }
4840: return false;
4841: }
4842:
4843: /**
4844: * Hash code method.
4845: *
4846: * @return the hash code.
4847: * @since JavaMail 1.4.4
4848: */
4849: @Override
4850: public final int hashCode() {
4851: return getClass().hashCode() + name.hashCode();
4852: }
4853:
4854: @Override
4855: public final String toString() {
4856: return name;
4857: }
4858: }
4859: }