Skip to content

Package: MailHandler$DefaultAuthenticator

MailHandler$DefaultAuthenticator

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