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