Skip to content

Package: POP3Store

POP3Store

nameinstructionbranchcomplexitylinemethod
POP3Store(Session, URLName)
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
POP3Store(Session, URLName, String, boolean)
M: 269 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 63 C: 0
0%
M: 1 C: 0
0%
authenticate(Protocol, String, String)
M: 181 C: 0
0%
M: 22 C: 0
0%
M: 12 C: 0
0%
M: 37 C: 0
0%
M: 1 C: 0
0%
capabilities()
M: 17 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
checkConnected()
M: 9 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
cleanupAndThrow(Protocol, IOException)
M: 35 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
close()
M: 4 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
close(boolean)
M: 20 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
closePort(POP3Folder)
M: 11 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
finalize()
M: 14 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getBoolProp(String)
M: 41 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
getDefaultFolder()
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getFolder(String)
M: 8 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getFolder(URLName)
M: 9 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
getPort(POP3Folder)
M: 170 C: 0
0%
M: 34 C: 0
0%
M: 18 C: 0
0%
M: 35 C: 0
0%
M: 1 C: 0
0%
getSession()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isConnected()
M: 32 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
isRecoverable(Throwable)
M: 10 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isSSL()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
protocolConnect(String, int, String, String)
M: 74 C: 0
0%
M: 10 C: 0
0%
M: 6 C: 0
0%
M: 19 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.pop3;
18:
19: import jakarta.mail.AuthenticationFailedException;
20: import jakarta.mail.Folder;
21: import jakarta.mail.MessagingException;
22: import jakarta.mail.Session;
23: import jakarta.mail.Store;
24: import jakarta.mail.URLName;
25: import org.eclipse.angus.mail.util.MailConnectException;
26: import org.eclipse.angus.mail.util.MailLogger;
27: import org.eclipse.angus.mail.util.PropUtil;
28: import org.eclipse.angus.mail.util.SocketConnectException;
29:
30: import java.io.EOFException;
31: import java.io.File;
32: import java.io.IOException;
33: import java.lang.reflect.Constructor;
34: import java.util.Collections;
35: import java.util.Locale;
36: import java.util.Map;
37: import java.util.StringTokenizer;
38: import java.util.logging.Level;
39:
40: /**
41: * A POP3 Message Store. Contains only one folder, "INBOX".
42: *
43: * See the <a href="package-summary.html">org.eclipse.angus.mail.pop3</a> package
44: * documentation for further information on the POP3 protocol provider. <p>
45: *
46: * @author Bill Shannon
47: * @author John Mani
48: */
49: public class POP3Store extends Store {
50:
51: private String name = "pop3"; // my protocol name
52: private int defaultPort = 110; // default POP3 port
53: private boolean isSSL = false; // use SSL?
54:
55: private Protocol port = null; // POP3 port for self
56: private POP3Folder portOwner = null; // folder owning port
57: private String host = null; // host
58: private int portNum = -1;
59: private String user = null;
60: private String passwd = null;
61: private boolean useStartTLS = false;
62: private boolean requireStartTLS = false;
63: private boolean usingSSL = false;
64: private Map<String, String> capabilities;
65: private MailLogger logger;
66:
67: // following set here and accessed by other classes in this package
68: volatile Constructor<?> messageConstructor = null;
69: volatile boolean rsetBeforeQuit = false;
70: volatile boolean disableTop = false;
71: volatile boolean forgetTopHeaders = false;
72: volatile boolean supportsUidl = true;
73: volatile boolean cacheWriteTo = false;
74: volatile boolean useFileCache = false;
75: volatile File fileCacheDir = null;
76: volatile boolean keepMessageContent = false;
77: volatile boolean finalizeCleanClose = false;
78:
79: public POP3Store(Session session, URLName url) {
80: this(session, url, "pop3", false);
81: }
82:
83: public POP3Store(Session session, URLName url,
84: String name, boolean isSSL) {
85: super(session, url);
86:• if (url != null)
87: name = url.getProtocol();
88: this.name = name;
89: logger = new MailLogger(this.getClass(), "DEBUG POP3",
90: session.getDebug(), session.getDebugOut());
91:
92:• if (!isSSL)
93: isSSL = PropUtil.getBooleanProperty(session.getProperties(),
94: "mail." + name + ".ssl.enable", false);
95:• if (isSSL)
96: this.defaultPort = 995;
97: else
98: this.defaultPort = 110;
99: this.isSSL = isSSL;
100:
101: rsetBeforeQuit = getBoolProp("rsetbeforequit");
102: disableTop = getBoolProp("disabletop");
103: forgetTopHeaders = getBoolProp("forgettopheaders");
104: cacheWriteTo = getBoolProp("cachewriteto");
105: useFileCache = getBoolProp("filecache.enable");
106: String dir = session.getProperty("mail." + name + ".filecache.dir");
107:• if (dir != null && logger.isLoggable(Level.CONFIG))
108: logger.config("mail." + name + ".filecache.dir: " + dir);
109:• if (dir != null)
110: fileCacheDir = new File(dir);
111: keepMessageContent = getBoolProp("keepmessagecontent");
112:
113: // mail.pop3.starttls.enable enables use of STLS command
114: useStartTLS = getBoolProp("starttls.enable");
115:
116: // mail.pop3.starttls.required requires use of STLS command
117: requireStartTLS = getBoolProp("starttls.required");
118:
119: // mail.pop3.finalizecleanclose requires clean close when finalizing
120: finalizeCleanClose = getBoolProp("finalizecleanclose");
121:
122: String s = session.getProperty("mail." + name + ".message.class");
123:• if (s != null) {
124: logger.log(Level.CONFIG, "message class: {0}", s);
125: try {
126: ClassLoader cl = this.getClass().getClassLoader();
127:
128: // now load the class
129: Class<?> messageClass = null;
130: try {
131: // First try the "application's" class loader.
132: // This should eventually be replaced by
133: // Thread.currentThread().getContextClassLoader().
134: messageClass = Class.forName(s, false, cl);
135: } catch (ClassNotFoundException ex1) {
136: // That didn't work, now try the "system" class loader.
137: // (Need both of these because JDK 1.1 class loaders
138: // may not delegate to their parent class loader.)
139: messageClass = Class.forName(s);
140: }
141:
142: Class<?>[] c = {jakarta.mail.Folder.class, int.class};
143: messageConstructor = messageClass.getConstructor(c);
144: } catch (Exception ex) {
145: logger.log(Level.CONFIG, "failed to load message class", ex);
146: }
147: }
148: }
149:
150: /**
151: * Get the value of a boolean property.
152: * Print out the value if logging is enabled.
153: */
154: private final synchronized boolean getBoolProp(String prop) {
155: prop = "mail." + name + "." + prop;
156: boolean val = PropUtil.getBooleanProperty(session.getProperties(),
157: prop, false);
158:• if (logger.isLoggable(Level.CONFIG))
159: logger.config(prop + ": " + val);
160: return val;
161: }
162:
163: /**
164: * Get a reference to the session.
165: */
166: synchronized Session getSession() {
167: return session;
168: }
169:
170: @Override
171: protected synchronized boolean protocolConnect(String host, int portNum,
172: String user, String passwd) throws MessagingException {
173:
174: // check for non-null values of host, password, user
175:• if (host == null || passwd == null || user == null)
176: return false;
177:
178: // if port is not specified, set it to value of mail.pop3.port
179: // property if it exists, otherwise default to 110
180:• if (portNum == -1)
181: portNum = PropUtil.getIntProperty(session.getProperties(),
182: "mail." + name + ".port", -1);
183:
184:• if (portNum == -1)
185: portNum = defaultPort;
186:
187: this.host = host;
188: this.portNum = portNum;
189: this.user = user;
190: this.passwd = passwd;
191: try {
192: port = getPort(null);
193: } catch (EOFException eex) {
194: throw new AuthenticationFailedException(eex.getMessage());
195: } catch (SocketConnectException scex) {
196: throw new MailConnectException(scex);
197: } catch (IOException ioex) {
198: throw new MessagingException("Connect failed", ioex);
199: }
200:
201: return true;
202: }
203:
204: /**
205: * Check whether this store is connected. Override superclass
206: * method, to actually ping our server connection.
207: */
208: /*
209: * Note that we maintain somewhat of an illusion of being connected
210: * even if we're not really connected. This is because a Folder
211: * can use the connection and close it when it's done. If we then
212: * ask whether the Store's connected we want the answer to be true,
213: * as long as we can reconnect at that point. This means that we
214: * need to be able to reconnect the Store on demand.
215: */
216: @Override
217: public synchronized boolean isConnected() {
218:• if (!super.isConnected())
219: // if we haven't been connected at all, don't bother with
220: // the NOOP.
221: return false;
222: try {
223:• if (port == null)
224: port = getPort(null);
225:• else if (!port.noop())
226: throw new IOException("NOOP failed");
227: return true;
228: } catch (IOException ioex) {
229: // no longer connected, close it down
230: try {
231: super.close(); // notifies listeners
232: } catch (MessagingException mex) {
233: // ignore it
234: }
235: return false;
236: }
237: }
238:
239: synchronized Protocol getPort(POP3Folder owner) throws IOException {
240: Protocol p;
241:
242: // if we already have a port, remember who's using it
243:• if (port != null && portOwner == null) {
244: portOwner = owner;
245: return port;
246: }
247:
248: // need a new port, create it and try to login
249: p = new Protocol(host, portNum, logger,
250: session.getProperties(), "mail." + name, isSSL);
251:
252:• if (useStartTLS || requireStartTLS) {
253:• if (p.hasCapability("STLS")) {
254:• if (p.stls()) {
255: // success, refresh capabilities
256: p.setCapabilities(p.capa());
257:• } else if (requireStartTLS) {
258: logger.fine("STLS required but failed");
259: throw cleanupAndThrow(p,
260: new EOFException("STLS required but failed"));
261: }
262:• } else if (requireStartTLS) {
263: logger.fine("STLS required but not supported");
264: throw cleanupAndThrow(p,
265: new EOFException("STLS required but not supported"));
266: }
267: }
268:
269: capabilities = p.getCapabilities(); // save for later, may be null
270: usingSSL = p.isSSL(); // in case anyone asks
271:
272: /*
273: * If we haven't explicitly disabled use of the TOP command,
274: * and the server has provided its capabilities,
275: * and the server doesn't support the TOP command,
276: * disable the TOP command.
277: */
278:• if (!disableTop &&
279:• capabilities != null && !capabilities.containsKey("TOP")) {
280: disableTop = true;
281: logger.fine("server doesn't support TOP, disabling it");
282: }
283:
284:• supportsUidl = capabilities == null || capabilities.containsKey("UIDL");
285:
286: try {
287:• if (!authenticate(p, user, passwd))
288: throw cleanupAndThrow(p, new EOFException("login failed"));
289: } catch (EOFException ex) {
290: throw cleanupAndThrow(p, ex);
291: } catch (Exception ex) {
292: throw cleanupAndThrow(p, new EOFException(ex.getMessage()));
293: }
294:
295:
296: /*
297: * If a Folder closes the port, and then a Folder
298: * is opened, the Store won't have a port. In that
299: * case, the getPort call will come from Folder.open,
300: * but we need to keep track of the port in the Store
301: * so that a later call to Folder.isOpen, which calls
302: * Store.isConnected, will use the same port.
303: */
304:• if (port == null && owner != null) {
305: port = p;
306: portOwner = owner;
307: }
308:• if (portOwner == null)
309: portOwner = owner;
310: return p;
311: }
312:
313: private static IOException cleanupAndThrow(Protocol p, IOException ife) {
314: try {
315: p.quit();
316: } catch (Throwable thr) {
317:• if (isRecoverable(thr)) {
318: ife.addSuppressed(thr);
319: } else {
320: thr.addSuppressed(ife);
321:• if (thr instanceof Error) {
322: throw (Error) thr;
323: }
324:• if (thr instanceof RuntimeException) {
325: throw (RuntimeException) thr;
326: }
327: throw new RuntimeException("unexpected exception", thr);
328: }
329: }
330: return ife;
331: }
332:
333: /**
334: * Authenticate to the server.
335: *
336: * XXX - This extensible authentication mechanism scheme was adapted
337: * from the SMTPTransport class. The work was done at the last
338: * minute for the 1.6.5 release and so is not as clean as it
339: * could be. There's great confusion over boolean success/failure
340: * return codes vs exceptions. This should all be cleaned up at
341: * some point, and more testing should be done, but I'm leaving
342: * it in this "I believe it works" state for now. I've tested
343: * it with LOGIN, PLAIN, and XOAUTH2 mechanisms, the latter being
344: * the primary motivation for the work right now.
345: *
346: * @param p the Protocol object to use
347: * @param user the user to authenticate as
348: * @param passwd the password for the user
349: * @return true if authentication succeeds
350: * @exception MessagingException if authentication fails
351: * @since Jakarta Mail 1.6.5
352: */
353: private boolean authenticate(Protocol p, String user, String passwd)
354: throws MessagingException {
355: // setting mail.pop3.auth.mechanisms controls which mechanisms will
356: // be used, and in what order they'll be considered. only the first
357: // match is used.
358: String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
359: boolean usingDefaultMechs = false;
360:• if (mechs == null) {
361: mechs = p.getDefaultMechanisms();
362: usingDefaultMechs = true;
363: }
364:
365: String authzid =
366: session.getProperty("mail." + name + ".sasl.authorizationid");
367:• if (authzid == null)
368: authzid = user;
369:         /*
370:          * XXX - maybe someday
371:          *
372:         if (enableSASL) {
373:          logger.fine("Authenticate with SASL");
374:          try {
375:                 if (sasllogin(getSASLMechanisms(), getSASLRealm(), authzid,
376:                                 user, passwd)) {
377:                  return true;        // success
378:                 } else {
379:                  logger.fine("SASL authentication failed");
380:                  return false;
381:                 }
382:          } catch (UnsupportedOperationException ex) {
383:                 logger.log(Level.FINE, "SASL support failed", ex);
384:                 // if the SASL support fails, fall back to non-SASL
385:          }
386:         }
387:          */
388:
389:• if (logger.isLoggable(Level.FINE))
390: logger.fine("Attempt to authenticate using mechanisms: " + mechs);
391:
392: /*
393: * Loop through the list of mechanisms supplied by the user
394: * (or defaulted) and try each in turn. If the server supports
395: * the mechanism and we have an authenticator for the mechanism,
396: * and it hasn't been disabled, use it.
397: */
398: StringTokenizer st = new StringTokenizer(mechs);
399:• while (st.hasMoreTokens()) {
400: String m = st.nextToken();
401: m = m.toUpperCase(Locale.ENGLISH);
402:• if (!p.supportsMechanism(m)) {
403: logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
404: continue;
405: }
406:
407:• if (!p.supportsAuthentication(m)) {
408: logger.log(Level.FINE, "mechanism {0} not supported by server",
409: m);
410: continue;
411: }
412:
413: /*
414: * If using the default mechanisms, check if this one is disabled.
415: */
416:• if (usingDefaultMechs) {
417: String dprop = "mail." + name + ".auth." +
418: m.toLowerCase(Locale.ENGLISH) + ".disable";
419: boolean disabled = PropUtil.getBooleanProperty(
420: session.getProperties(),
421:• dprop, !p.isMechanismEnabled(m));
422:• if (disabled) {
423:• if (logger.isLoggable(Level.FINE))
424: logger.fine("mechanism " + m +
425: " disabled by property: " + dprop);
426: continue;
427: }
428: }
429:
430: // only the first supported and enabled mechanism is used
431: logger.log(Level.FINE, "Using mechanism {0}", m);
432: String msg =
433: p.authenticate(m, host, authzid, user, passwd);
434:• if (msg != null)
435: throw new AuthenticationFailedException(msg);
436: return true;
437: }
438:
439: // if no authentication mechanism found, fail
440: throw new AuthenticationFailedException(
441: "No authentication mechanisms supported by both server and client");
442: }
443:
444: private static boolean isRecoverable(Throwable t) {
445:• return (t instanceof Exception) || (t instanceof LinkageError);
446: }
447:
448: synchronized void closePort(POP3Folder owner) {
449:• if (portOwner == owner) {
450: port = null;
451: portOwner = null;
452: }
453: }
454:
455: @Override
456: public synchronized void close() throws MessagingException {
457: close(false);
458: }
459:
460: synchronized void close(boolean force) throws MessagingException {
461: try {
462:• if (port != null) {
463:• if (force)
464: port.close();
465: else
466: port.quit();
467: }
468: } catch (IOException ioex) {
469: } finally {
470: port = null;
471:
472: // to set the state and send the closed connection event
473: super.close();
474: }
475: }
476:
477: @Override
478: public Folder getDefaultFolder() throws MessagingException {
479: checkConnected();
480: return new DefaultFolder(this);
481: }
482:
483: /**
484: * Only the name "INBOX" is supported.
485: */
486: @Override
487: public Folder getFolder(String name) throws MessagingException {
488: checkConnected();
489: return new POP3Folder(this, name);
490: }
491:
492: @Override
493: public Folder getFolder(URLName url) throws MessagingException {
494: checkConnected();
495: return new POP3Folder(this, url.getFile());
496: }
497:
498: /**
499: * Return a Map of the capabilities the server provided,
500: * as per RFC 2449. If the server doesn't support RFC 2449,
501: * an emtpy Map is returned. The returned Map can not be modified.
502: * The key to the Map is the upper case capability name as
503: * a String. The value of the entry is the entire String
504: * capability line returned by the server. <p>
505: *
506: * For example, to check if the server supports the STLS capability, use:
507: * <code>if (store.capabilities().containsKey("STLS")) ...</code>
508: *
509: * @return Map of capabilities
510: * @exception MessagingException for failures
511: * @since JavaMail 1.4.3
512: */
513: public Map<String, String> capabilities() throws MessagingException {
514: Map<String, String> c;
515: synchronized (this) {
516: c = capabilities;
517: }
518:• if (c != null)
519: return Collections.unmodifiableMap(c);
520: else
521: return Collections.<String, String>emptyMap();
522: }
523:
524: /**
525: * Is this POP3Store using SSL to connect to the server?
526: *
527: * @return true if using SSL
528: * @since JavaMail 1.4.6
529: */
530: public synchronized boolean isSSL() {
531: return usingSSL;
532: }
533:
534: @Override
535: protected void finalize() throws Throwable {
536: try {
537:• if (port != null) // don't force a connection attempt
538:• close(!finalizeCleanClose);
539: } finally {
540: super.finalize();
541: }
542: }
543:
544: private void checkConnected() throws MessagingException {
545:• if (!super.isConnected())
546: throw new MessagingException("Not connected");
547: }
548: }