Search in sources :

Example 1 with EmailHeader

use of com.google.gerrit.entities.EmailHeader in project gerrit by GerritCodeReview.

the class SmtpEmailSender method render.

private void render(Writer out, Map<String, EmailHeader> callerHeaders, String textBody, @Nullable String htmlBody) throws IOException, EmailException {
    final Map<String, EmailHeader> hdrs = new LinkedHashMap<>(callerHeaders);
    setMissingHeader(hdrs, "MIME-Version", "1.0");
    setMissingHeader(hdrs, "Content-Transfer-Encoding", "8bit");
    setMissingHeader(hdrs, "Content-Disposition", "inline");
    setMissingHeader(hdrs, "User-Agent", "Gerrit/" + Version.getVersion());
    if (importance != null) {
        setMissingHeader(hdrs, "Importance", importance);
    }
    if (expiryDays > 0) {
        Instant expiry = Instant.ofEpochMilli(TimeUtil.nowMs() + expiryDays * 24 * 60 * 60 * 1000L);
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss Z").withZone(ZoneId.systemDefault());
        setMissingHeader(hdrs, "Expiry-Date", fmt.format(expiry));
    }
    String encodedBody;
    if (htmlBody == null) {
        setMissingHeader(hdrs, "Content-Type", "text/plain; charset=UTF-8");
        encodedBody = textBody;
    } else {
        String boundary = generateMultipartBoundary(textBody, htmlBody);
        setMissingHeader(hdrs, "Content-Type", "multipart/alternative; boundary=\"" + boundary + "\"; charset=UTF-8");
        encodedBody = buildMultipartBody(boundary, textBody, htmlBody);
    }
    try (Writer w = new BufferedWriter(out)) {
        for (Map.Entry<String, EmailHeader> h : hdrs.entrySet()) {
            if (!h.getValue().isEmpty()) {
                w.write(h.getKey());
                w.write(": ");
                h.getValue().write(w);
                w.write("\r\n");
            }
        }
        w.write("\r\n");
        w.write(encodedBody);
        w.flush();
    }
}
Also used : EmailHeader(com.google.gerrit.entities.EmailHeader) StringEmailHeader(com.google.gerrit.entities.EmailHeader.StringEmailHeader) Instant(java.time.Instant) DateTimeFormatter(java.time.format.DateTimeFormatter) LinkedHashMap(java.util.LinkedHashMap) Map(java.util.Map) BufferedWriter(java.io.BufferedWriter) Writer(java.io.Writer) LinkedHashMap(java.util.LinkedHashMap) BufferedWriter(java.io.BufferedWriter)

Example 2 with EmailHeader

use of com.google.gerrit.entities.EmailHeader in project gerrit by GerritCodeReview.

the class MailSenderIT method headerString.

private String headerString(Map<String, EmailHeader> headers, String name) {
    EmailHeader header = headers.get(name);
    assertThat(header).isInstanceOf(StringEmailHeader.class);
    return ((StringEmailHeader) header).getString();
}
Also used : StringEmailHeader(com.google.gerrit.entities.EmailHeader.StringEmailHeader) EmailHeader(com.google.gerrit.entities.EmailHeader) StringEmailHeader(com.google.gerrit.entities.EmailHeader.StringEmailHeader)

Example 3 with EmailHeader

use of com.google.gerrit.entities.EmailHeader in project gerrit by GerritCodeReview.

the class OutgoingEmail method sendImpl.

private void sendImpl() throws EmailException {
    if (!args.emailSender.isEnabled()) {
        // Server has explicitly disabled email sending.
        // 
        logger.atFine().log("Not sending '%s': Email sending is disabled by server config", messageClass);
        return;
    }
    if (!notify.shouldNotify()) {
        logger.atFine().log("Not sending '%s': Notify handling is NONE", messageClass);
        return;
    }
    init();
    if (messageId == null) {
        throw new IllegalStateException("All emails must have a messageId");
    }
    format();
    appendText(textTemplate("Footer"));
    if (useHtml()) {
        appendHtml(soyHtmlTemplate("FooterHtml"));
    }
    Set<Address> smtpRcptToPlaintextOnly = new HashSet<>();
    if (shouldSendMessage()) {
        if (fromId != null) {
            Optional<AccountState> fromUser = args.accountCache.get(fromId);
            if (fromUser.isPresent()) {
                GeneralPreferencesInfo senderPrefs = fromUser.get().generalPreferences();
                CurrentUser user = args.currentUserProvider.get();
                boolean isImpersonating = user.isIdentifiedUser() && user.isImpersonating();
                if (isImpersonating && user.getAccountId() != fromId) {
                    // set up correctly.
                    throw new EmailException(String.format("User %s is sending email from %s, while acting on behalf of %s", user.asIdentifiedUser().getRealUser().getAccountId(), fromId, user.getAccountId()));
                }
                if (senderPrefs != null && senderPrefs.getEmailStrategy() == CC_ON_OWN_COMMENTS) {
                    // Include the sender in email if they enabled email notifications on their own
                    // comments.
                    // 
                    logger.atFine().log("CC email sender %s because the email strategy of this user is %s", fromUser.get().account().id(), CC_ON_OWN_COMMENTS);
                    add(RecipientType.CC, fromId);
                } else if (isImpersonating) {
                    // If we are impersonating a user, make sure they receive a CC of
                    // this message regardless of email strategy, unless email notifications are explicitly
                    // disabled for this user. This way they can always review and audit what we sent
                    // on their behalf to others.
                    logger.atFine().log("CC email sender %s because the email is sent on behalf of and email notifications" + " are enabled for this user.", fromUser.get().account().id());
                    add(RecipientType.CC, fromId);
                } else if (!notify.accounts().containsValue(fromId) && rcptTo.remove(fromId)) {
                    // If they don't want a copy, but we queued one up anyway,
                    // drop them from the recipient lists, but only if the user is not being impersonated.
                    // 
                    logger.atFine().log("Not CCing email sender %s because the email strategy of this user is not %s but" + " %s", fromUser.get().account().id(), CC_ON_OWN_COMMENTS, senderPrefs != null ? senderPrefs.getEmailStrategy() : null);
                    removeUser(fromUser.get().account());
                }
            }
        }
        // In addition, check if users only want to receive plaintext email.
        for (Account.Id id : rcptTo) {
            Optional<AccountState> thisUser = args.accountCache.get(id);
            if (thisUser.isPresent()) {
                Account thisUserAccount = thisUser.get().account();
                GeneralPreferencesInfo prefs = thisUser.get().generalPreferences();
                if (prefs == null || prefs.getEmailStrategy() == DISABLED) {
                    logger.atFine().log("Not emailing account %s because user has set email strategy to %s", id, DISABLED);
                    removeUser(thisUserAccount);
                } else if (useHtml() && prefs.getEmailFormat() == EmailFormat.PLAINTEXT) {
                    logger.atFine().log("Removing account %s from HTML email because user prefers plain text emails", id);
                    removeUser(thisUserAccount);
                    smtpRcptToPlaintextOnly.add(Address.create(thisUserAccount.fullName(), thisUserAccount.preferredEmail()));
                }
            }
            if (smtpRcptTo.isEmpty() && smtpRcptToPlaintextOnly.isEmpty()) {
                logger.atFine().log("Not sending '%s': No SMTP recipients", messageClass);
                return;
            }
        }
        // inbound email replies.
        if (!headers.containsKey(FieldName.REPLY_TO)) {
            StringJoiner j = new StringJoiner(", ");
            if (fromId != null) {
                Address address = toAddress(fromId);
                if (address != null) {
                    j.add(address.email());
                }
            }
            // For users who prefer plaintext, this comes at the cost of not being
            // listed in the multipart To and Cc headers. We work around this by adding
            // all users to the Reply-To address in both the plaintext and multipart
            // email. We should exclude any BCC addresses from reply-to, because they should be
            // invisible to other recipients.
            Sets.difference(Sets.union(smtpRcptTo, smtpRcptToPlaintextOnly), smtpBccRcptTo).stream().forEach(a -> j.add(a.email()));
            setHeader(FieldName.REPLY_TO, j.toString());
        }
        String textPart = textBody.toString();
        OutgoingEmailValidationListener.Args va = new OutgoingEmailValidationListener.Args();
        va.messageClass = messageClass;
        va.smtpFromAddress = smtpFromAddress;
        va.smtpRcptTo = smtpRcptTo;
        va.headers = headers;
        va.body = textPart;
        if (useHtml()) {
            va.htmlBody = htmlBody.toString();
        } else {
            va.htmlBody = null;
        }
        Set<Address> intersection = Sets.intersection(va.smtpRcptTo, smtpRcptToPlaintextOnly);
        if (!intersection.isEmpty()) {
            logger.atSevere().log("Email '%s' will be sent twice to %s", messageClass, intersection);
        }
        if (!va.smtpRcptTo.isEmpty()) {
            // Send multipart message
            addMessageId(va, "-HTML");
            if (!validateEmail(va))
                return;
            logger.atFine().log("Sending multipart '%s' from %s to %s", messageClass, va.smtpFromAddress, va.smtpRcptTo);
            args.emailSender.send(va.smtpFromAddress, va.smtpRcptTo, va.headers, va.body, va.htmlBody);
        }
        if (!smtpRcptToPlaintextOnly.isEmpty()) {
            addMessageId(va, "-PLAIN");
            // Send plaintext message
            Map<String, EmailHeader> shallowCopy = new HashMap<>();
            shallowCopy.putAll(headers);
            // Remove To and Cc
            shallowCopy.remove(FieldName.TO);
            shallowCopy.remove(FieldName.CC);
            for (Address a : smtpRcptToPlaintextOnly) {
                // Add new To
                EmailHeader.AddressList to = new EmailHeader.AddressList();
                to.add(a);
                shallowCopy.put(FieldName.TO, to);
            }
            if (!validateEmail(va))
                return;
            logger.atFine().log("Sending plaintext '%s' from %s to %s", messageClass, va.smtpFromAddress, smtpRcptToPlaintextOnly);
            args.emailSender.send(va.smtpFromAddress, smtpRcptToPlaintextOnly, shallowCopy, va.body);
        }
    }
}
Also used : Account(com.google.gerrit.entities.Account) Address(com.google.gerrit.entities.Address) CurrentUser(com.google.gerrit.server.CurrentUser) EmailHeader(com.google.gerrit.entities.EmailHeader) StringEmailHeader(com.google.gerrit.entities.EmailHeader.StringEmailHeader) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) AccountState(com.google.gerrit.server.account.AccountState) OutgoingEmailValidationListener(com.google.gerrit.server.validators.OutgoingEmailValidationListener) AddressList(com.google.gerrit.entities.EmailHeader.AddressList) EmailException(com.google.gerrit.exceptions.EmailException) GeneralPreferencesInfo(com.google.gerrit.extensions.client.GeneralPreferencesInfo) AddressList(com.google.gerrit.entities.EmailHeader.AddressList) StringJoiner(java.util.StringJoiner) HashSet(java.util.HashSet)

Aggregations

EmailHeader (com.google.gerrit.entities.EmailHeader)3 StringEmailHeader (com.google.gerrit.entities.EmailHeader.StringEmailHeader)3 LinkedHashMap (java.util.LinkedHashMap)2 Account (com.google.gerrit.entities.Account)1 Address (com.google.gerrit.entities.Address)1 AddressList (com.google.gerrit.entities.EmailHeader.AddressList)1 EmailException (com.google.gerrit.exceptions.EmailException)1 GeneralPreferencesInfo (com.google.gerrit.extensions.client.GeneralPreferencesInfo)1 CurrentUser (com.google.gerrit.server.CurrentUser)1 AccountState (com.google.gerrit.server.account.AccountState)1 OutgoingEmailValidationListener (com.google.gerrit.server.validators.OutgoingEmailValidationListener)1 BufferedWriter (java.io.BufferedWriter)1 Writer (java.io.Writer)1 Instant (java.time.Instant)1 DateTimeFormatter (java.time.format.DateTimeFormatter)1 HashMap (java.util.HashMap)1 HashSet (java.util.HashSet)1 Map (java.util.Map)1 StringJoiner (java.util.StringJoiner)1