LCOV - code coverage report
Current view: top level - server/mail/receive - ImapMailReceiver.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 36 48 75.0 %
Date: 2022-11-19 15:00:39 Functions: 3 3 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2016 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.mail.receive;
      16             : 
      17             : import com.google.common.flogger.FluentLogger;
      18             : import com.google.gerrit.mail.MailMessage;
      19             : import com.google.gerrit.mail.MailParsingException;
      20             : import com.google.gerrit.mail.RawMailParser;
      21             : import com.google.gerrit.server.git.WorkQueue;
      22             : import com.google.gerrit.server.mail.EmailSettings;
      23             : import com.google.gerrit.server.mail.Encryption;
      24             : import com.google.inject.Inject;
      25             : import com.google.inject.Singleton;
      26             : import java.io.IOException;
      27             : import java.util.ArrayList;
      28             : import java.util.List;
      29             : import org.apache.commons.net.imap.IMAPClient;
      30             : import org.apache.commons.net.imap.IMAPSClient;
      31             : 
      32             : @Singleton
      33             : public class ImapMailReceiver extends MailReceiver {
      34           1 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      35             : 
      36             :   private static final String INBOX_FOLDER = "INBOX";
      37             : 
      38             :   @Inject
      39             :   ImapMailReceiver(EmailSettings mailSettings, MailProcessor mailProcessor, WorkQueue workQueue) {
      40           1 :     super(mailSettings, mailProcessor, workQueue);
      41           1 :   }
      42             : 
      43             :   /**
      44             :    * Opens a connection to the mail server, removes emails where deletion is pending, reads new
      45             :    * email and closes the connection.
      46             :    *
      47             :    * @param async determines if processing messages should happen asynchronously
      48             :    * @throws MailTransferException in case of a known transport failure
      49             :    * @throws IOException in case of a low-level transport failure
      50             :    */
      51             :   @Override
      52             :   public synchronized void handleEmails(boolean async) throws MailTransferException, IOException {
      53             :     IMAPClient imap;
      54           1 :     if (mailSettings.encryption != Encryption.NONE) {
      55           0 :       imap = new IMAPSClient(mailSettings.encryption.name(), true);
      56             :     } else {
      57           1 :       imap = new IMAPClient();
      58             :     }
      59           1 :     if (mailSettings.port > 0) {
      60           1 :       imap.setDefaultPort(mailSettings.port);
      61             :     }
      62             :     // Set a 30s timeout for each operation
      63           1 :     imap.setDefaultTimeout(30 * 1000);
      64           1 :     imap.connect(mailSettings.host);
      65             :     try {
      66           1 :       if (!imap.login(mailSettings.username, mailSettings.password)) {
      67           0 :         throw new MailTransferException("Could not login to IMAP server");
      68             :       }
      69             :       try {
      70           1 :         if (!imap.select(INBOX_FOLDER)) {
      71           0 :           throw new MailTransferException("Could not select IMAP folder " + INBOX_FOLDER);
      72             :         }
      73             :         // Fetch just the internal dates first to know how many messages we
      74             :         // should fetch.
      75           1 :         if (!imap.fetch("1:*", "(INTERNALDATE)")) {
      76             :           // false indicates that there are no messages to fetch
      77           0 :           logger.atInfo().log("Fetched 0 messages via IMAP");
      78           0 :           return;
      79             :         }
      80             :         // Format of reply is one line per email and one line to indicate
      81             :         // that the fetch was successful.
      82             :         // Example:
      83             :         // * 1 FETCH (INTERNALDATE "Mon, 24 Oct 2016 16:53:22 +0200 (CEST)")
      84             :         // * 2 FETCH (INTERNALDATE "Mon, 24 Oct 2016 16:53:22 +0200 (CEST)")
      85             :         // AAAC OK FETCH completed.
      86           1 :         int numMessages = imap.getReplyStrings().length - 1;
      87           1 :         logger.atInfo().log("Fetched %d messages via IMAP", numMessages);
      88             :         // Fetch the full version of all emails
      89           1 :         List<MailMessage> mailMessages = new ArrayList<>(numMessages);
      90           1 :         for (int i = 1; i <= numMessages; i++) {
      91           1 :           if (imap.fetch(i + ":" + i, "(BODY.PEEK[])")) {
      92             :             // Obtain full reply
      93           1 :             String[] rawMessage = imap.getReplyStrings();
      94           1 :             if (rawMessage.length < 2) {
      95           0 :               continue;
      96             :             }
      97             :             // First and last line are IMAP status codes. We have already
      98             :             // checked, that the fetch returned true (OK), so we safely ignore
      99             :             // those two lines.
     100           1 :             StringBuilder b = new StringBuilder(2 * (rawMessage.length - 2));
     101           1 :             for (int j = 1; j < rawMessage.length - 1; j++) {
     102           1 :               if (j > 1) {
     103           1 :                 b.append("\n");
     104             :               }
     105           1 :               b.append(rawMessage[j]);
     106             :             }
     107             :             try {
     108           1 :               MailMessage mailMessage = RawMailParser.parse(b.toString());
     109           1 :               if (pendingDeletion.contains(mailMessage.id())) {
     110             :                 // Mark message as deleted
     111           1 :                 if (imap.store(i + ":" + i, "+FLAGS", "(\\Deleted)")) {
     112           1 :                   pendingDeletion.remove(mailMessage.id());
     113             :                 } else {
     114           0 :                   logger.atSevere().log(
     115           0 :                       "Could not mark mail message as deleted: %s", mailMessage.id());
     116             :                 }
     117             :               } else {
     118           1 :                 mailMessages.add(mailMessage);
     119             :               }
     120           0 :             } catch (MailParsingException e) {
     121           0 :               logger.atSevere().withCause(e).log("Exception while parsing email after IMAP fetch");
     122           1 :             }
     123           1 :           } else {
     124           0 :             logger.atSevere().log("IMAP fetch failed. Will retry in next fetch cycle.");
     125             :           }
     126             :         }
     127             :         // Permanently delete emails marked for deletion
     128           1 :         if (!imap.expunge()) {
     129           0 :           logger.atSevere().log("Could not expunge IMAP emails");
     130             :         }
     131           1 :         dispatchMailProcessor(mailMessages, async);
     132             :       } finally {
     133           1 :         imap.logout();
     134             :       }
     135             :     } finally {
     136           1 :       imap.disconnect();
     137             :     }
     138           1 :   }
     139             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750