Send notification emails when inbound emails are rejected
With this commit, explanatory messages are sent when inbound messages are rejected. Four error types are defined: PARSING_ERROR, INACTIVE_ACCOUNT, UNKNOWN_ACCOUNT, and INTERNAL_EXCEPTION. PARSING_ERROR (probably the most frequent one) occurs when the Gerrit metadatas can't be parsed. INACTIVE_ACCOUNT occurs when the user's account is Inactive. UNKNOWN_ACCOUNT occurs when zero or more than one accounts are found for the incoming email address. This might be caused by multiple sources providing the same user. INTERNAL_EXCEPTION is used for all the other exceptions that can't be described properly to the end user. For now, it is only fired when two Changes are found with the same Id. This _should_ be a rare exception, but it might occur. This change also introduces a new MailHeader enum, and removes the old MetadataName one. This allows for a cleaner way to define both Mail headers and Gerrit "internal" metadata names. Feature: Issue 8210 Change-Id: I48a081f2ce1be391b9f3ff991760740d5ada3357
This commit is contained in:

committed by
David Pursehouse

parent
96251c8d71
commit
2a10edcddc
@@ -1508,7 +1508,7 @@ public abstract class AbstractDaemonTest {
|
||||
assertThat(m.rcpt()).containsExactly(expected);
|
||||
assertThat(((EmailHeader.AddressList) m.headers().get("To")).getAddressList())
|
||||
.containsExactly(expected);
|
||||
assertThat(m.headers().get("CC").isEmpty()).isTrue();
|
||||
assertThat(m.headers().get("Cc").isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
protected void assertNotifyCc(TestAccount expected) {
|
||||
@@ -1520,7 +1520,7 @@ public abstract class AbstractDaemonTest {
|
||||
Message m = sender.getMessages().get(0);
|
||||
assertThat(m.rcpt()).containsExactly(expected);
|
||||
assertThat(m.headers().get("To").isEmpty()).isTrue();
|
||||
assertThat(((EmailHeader.AddressList) m.headers().get("CC")).getAddressList())
|
||||
assertThat(((EmailHeader.AddressList) m.headers().get("Cc")).getAddressList())
|
||||
.containsExactly(expected);
|
||||
}
|
||||
|
||||
@@ -1529,7 +1529,7 @@ public abstract class AbstractDaemonTest {
|
||||
Message m = sender.getMessages().get(0);
|
||||
assertThat(m.rcpt()).containsExactly(expected.emailAddress);
|
||||
assertThat(m.headers().get("To").isEmpty()).isTrue();
|
||||
assertThat(m.headers().get("CC").isEmpty()).isTrue();
|
||||
assertThat(m.headers().get("Cc").isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
protected interface ProjectWatchInfoConfiguration {
|
||||
|
@@ -113,7 +113,7 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
|
||||
}
|
||||
recipients = new HashMap<>();
|
||||
recipients.put(TO, parseAddresses(message, "To"));
|
||||
recipients.put(CC, parseAddresses(message, "CC"));
|
||||
recipients.put(CC, parseAddresses(message, "Cc"));
|
||||
recipients.put(
|
||||
BCC,
|
||||
message
|
||||
|
@@ -121,6 +121,8 @@ public class SitePathInitializer {
|
||||
extractMailExample("Footer.soy");
|
||||
extractMailExample("FooterHtml.soy");
|
||||
extractMailExample("HeaderHtml.soy");
|
||||
extractMailExample("InboundEmailRejection.soy");
|
||||
extractMailExample("InboundEmailRejectionHtml.soy");
|
||||
extractMailExample("Merged.soy");
|
||||
extractMailExample("MergedHtml.soy");
|
||||
extractMailExample("NewChange.soy");
|
||||
|
@@ -136,6 +136,7 @@ import com.google.gerrit.server.git.validators.RefOperationValidators;
|
||||
import com.google.gerrit.server.git.validators.UploadValidationListener;
|
||||
import com.google.gerrit.server.git.validators.UploadValidators;
|
||||
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
|
||||
import com.google.gerrit.server.mail.AutoReplyMailFilter;
|
||||
import com.google.gerrit.server.mail.EmailModule;
|
||||
import com.google.gerrit.server.mail.ListMailFilter;
|
||||
import com.google.gerrit.server.mail.MailFilter;
|
||||
@@ -145,6 +146,7 @@ import com.google.gerrit.server.mail.send.CreateChangeSender;
|
||||
import com.google.gerrit.server.mail.send.DeleteReviewerSender;
|
||||
import com.google.gerrit.server.mail.send.FromAddressGenerator;
|
||||
import com.google.gerrit.server.mail.send.FromAddressGeneratorProvider;
|
||||
import com.google.gerrit.server.mail.send.InboundEmailRejectionSender;
|
||||
import com.google.gerrit.server.mail.send.MailSoyTofuProvider;
|
||||
import com.google.gerrit.server.mail.send.MailTemplates;
|
||||
import com.google.gerrit.server.mail.send.MergedSender;
|
||||
@@ -265,6 +267,7 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
factory(RegisterNewEmailSender.Factory.class);
|
||||
factory(ReplacePatchSetSender.Factory.class);
|
||||
factory(SetAssigneeSender.Factory.class);
|
||||
factory(InboundEmailRejectionSender.Factory.class);
|
||||
bind(PermissionCollection.Factory.class);
|
||||
bind(AccountVisibility.class).toProvider(AccountVisibilityProvider.class).in(SINGLETON);
|
||||
factory(ProjectOwnerGroupsProvider.Factory.class);
|
||||
@@ -391,6 +394,9 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
|
||||
DynamicMap.mapOf(binder(), MailFilter.class);
|
||||
bind(MailFilter.class).annotatedWith(Exports.named("ListMailFilter")).to(ListMailFilter.class);
|
||||
bind(AutoReplyMailFilter.class)
|
||||
.annotatedWith(Exports.named("AutoReplyMailFilter"))
|
||||
.to(AutoReplyMailFilter.class);
|
||||
|
||||
factory(UploadValidators.Factory.class);
|
||||
DynamicSet.setOf(binder(), UploadValidationListener.class);
|
||||
|
55
java/com/google/gerrit/server/mail/AutoReplyMailFilter.java
Normal file
55
java/com/google/gerrit/server/mail/AutoReplyMailFilter.java
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (C) 2018 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail;
|
||||
|
||||
import com.google.gerrit.server.mail.receive.MailMessage;
|
||||
import com.google.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Filters out auto-reply messages according to RFC 3834. */
|
||||
@Singleton
|
||||
public class AutoReplyMailFilter implements MailFilter {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AutoReplyMailFilter.class);
|
||||
|
||||
@Override
|
||||
public boolean shouldProcessMessage(MailMessage message) {
|
||||
for (String header : message.additionalHeaders()) {
|
||||
if (header.startsWith(MailHeader.PRECEDENCE.fieldWithDelimiter())) {
|
||||
String prec = header.substring(MailHeader.PRECEDENCE.fieldWithDelimiter().length()).trim();
|
||||
|
||||
if (prec.equals("list") || prec.equals("junk") || prec.equals("bulk")) {
|
||||
log.error(
|
||||
"Message %s has a Precedence header. Will ignore and delete message.", message.id());
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (header.startsWith(MailHeader.AUTO_SUBMITTED.fieldWithDelimiter())) {
|
||||
String autoSubmitted =
|
||||
header.substring(MailHeader.AUTO_SUBMITTED.fieldWithDelimiter().length()).trim();
|
||||
|
||||
if (!autoSubmitted.equals("no")) {
|
||||
log.error(
|
||||
"Message %s has an Auto-Submitted header. Will ignore and delete message.",
|
||||
message.id());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
71
java/com/google/gerrit/server/mail/MailHeader.java
Normal file
71
java/com/google/gerrit/server/mail/MailHeader.java
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2018 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail;
|
||||
|
||||
/** Variables used by emails to hold data */
|
||||
public enum MailHeader {
|
||||
// Gerrit metadata holders
|
||||
ASSIGNEE("Gerrit-Assignee"),
|
||||
BRANCH("Gerrit-Branch"),
|
||||
CC("Gerit-CC"),
|
||||
COMMENT_IN_REPLY_TO("Comment-In-Reply-To"),
|
||||
COMMENT_DATE("Gerrit-Comment-Date"),
|
||||
CHANGE_ID("Gerrit-Change-Id"),
|
||||
CHANGE_NUMBER("Gerrit-Change-Number"),
|
||||
CHANGE_URL("Gerrit-ChangeURL"),
|
||||
COMMIT("Gerrit-Commit"),
|
||||
HAS_COMMENTS("Gerrit-HasComments"),
|
||||
HAS_LABELS("Gerrit-Has-Labels"),
|
||||
MESSAGE_TYPE("Gerrit-MessageType"),
|
||||
OWNER("Gerrit-Owner"),
|
||||
PATCH_SET("Gerrit-PatchSet"),
|
||||
PROJECT("Gerrit-Project"),
|
||||
REVIEWER("Gerrit-Reviewer"),
|
||||
|
||||
// Commonly used Email headers
|
||||
AUTO_SUBMITTED("Auto-Submitted"),
|
||||
PRECEDENCE("Precedence"),
|
||||
REFERENCES("References");
|
||||
|
||||
private final String name;
|
||||
private final String fieldName;
|
||||
|
||||
MailHeader(String name) {
|
||||
boolean customHeader = name.startsWith("Gerrit-");
|
||||
this.name = name;
|
||||
|
||||
if (customHeader) {
|
||||
this.fieldName = "X-" + name;
|
||||
} else {
|
||||
this.fieldName = name;
|
||||
}
|
||||
}
|
||||
|
||||
public String fieldWithDelimiter() {
|
||||
return fieldName() + ": ";
|
||||
}
|
||||
|
||||
public String withDelimiter() {
|
||||
return name + ": ";
|
||||
}
|
||||
|
||||
public String fieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
// Copyright (C) 2016 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail;
|
||||
|
||||
public final class MetadataName {
|
||||
public static final String CHANGE_NUMBER = "Gerrit-Change-Number";
|
||||
public static final String PATCH_SET = "Gerrit-PatchSet";
|
||||
public static final String MESSAGE_TYPE = "Gerrit-MessageType";
|
||||
public static final String TIMESTAMP = "Gerrit-Comment-Date";
|
||||
|
||||
public static String toHeader(String metadataName) {
|
||||
return "X-" + metadataName;
|
||||
}
|
||||
|
||||
public static String toHeaderWithDelimiter(String metadataName) {
|
||||
return toHeader(metadataName) + ": ";
|
||||
}
|
||||
|
||||
public static String toFooterWithDelimiter(String metadataName) {
|
||||
return metadataName + ": ";
|
||||
}
|
||||
}
|
@@ -14,14 +14,11 @@
|
||||
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
import static com.google.gerrit.server.mail.MetadataName.toFooterWithDelimiter;
|
||||
import static com.google.gerrit.server.mail.MetadataName.toHeaderWithDelimiter;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import com.google.gerrit.server.mail.MailUtil;
|
||||
import com.google.gerrit.server.mail.MetadataName;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeParseException;
|
||||
@@ -29,8 +26,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Parse metadata from inbound email */
|
||||
public class MetadataParser {
|
||||
private static final Logger log = LoggerFactory.getLogger(MetadataParser.class);
|
||||
public class MailHeaderParser {
|
||||
private static final Logger log = LoggerFactory.getLogger(MailHeaderParser.class);
|
||||
|
||||
public static MailMetadata parse(MailMessage m) {
|
||||
MailMetadata metadata = new MailMetadata();
|
||||
@@ -39,22 +36,22 @@ public class MetadataParser {
|
||||
|
||||
// Check email headers for X-Gerrit-<Name>
|
||||
for (String header : m.additionalHeaders()) {
|
||||
if (header.startsWith(toHeaderWithDelimiter(MetadataName.CHANGE_NUMBER))) {
|
||||
String num = header.substring(toHeaderWithDelimiter(MetadataName.CHANGE_NUMBER).length());
|
||||
if (header.startsWith(MailHeader.CHANGE_NUMBER.fieldWithDelimiter())) {
|
||||
String num = header.substring(MailHeader.CHANGE_NUMBER.fieldWithDelimiter().length());
|
||||
metadata.changeNumber = Ints.tryParse(num);
|
||||
} else if (header.startsWith(toHeaderWithDelimiter(MetadataName.PATCH_SET))) {
|
||||
String ps = header.substring(toHeaderWithDelimiter(MetadataName.PATCH_SET).length());
|
||||
} else if (header.startsWith(MailHeader.PATCH_SET.fieldWithDelimiter())) {
|
||||
String ps = header.substring(MailHeader.PATCH_SET.fieldWithDelimiter().length());
|
||||
metadata.patchSet = Ints.tryParse(ps);
|
||||
} else if (header.startsWith(toHeaderWithDelimiter(MetadataName.TIMESTAMP))) {
|
||||
String ts = header.substring(toHeaderWithDelimiter(MetadataName.TIMESTAMP).length()).trim();
|
||||
} else if (header.startsWith(MailHeader.COMMENT_DATE.fieldWithDelimiter())) {
|
||||
String ts = header.substring(MailHeader.COMMENT_DATE.fieldWithDelimiter().length()).trim();
|
||||
try {
|
||||
metadata.timestamp = Timestamp.from(MailUtil.rfcDateformatter.parse(ts, Instant::from));
|
||||
} catch (DateTimeParseException e) {
|
||||
log.error("Mail: Error while parsing timestamp from header of message " + m.id(), e);
|
||||
}
|
||||
} else if (header.startsWith(toHeaderWithDelimiter(MetadataName.MESSAGE_TYPE))) {
|
||||
} else if (header.startsWith(MailHeader.MESSAGE_TYPE.fieldWithDelimiter())) {
|
||||
metadata.messageType =
|
||||
header.substring(toHeaderWithDelimiter(MetadataName.MESSAGE_TYPE).length());
|
||||
header.substring(MailHeader.MESSAGE_TYPE.fieldWithDelimiter().length());
|
||||
}
|
||||
}
|
||||
if (metadata.hasRequiredFields()) {
|
||||
@@ -85,22 +82,21 @@ public class MetadataParser {
|
||||
|
||||
private static void extractFooters(Iterable<String> lines, MailMetadata metadata, MailMessage m) {
|
||||
for (String line : lines) {
|
||||
if (metadata.changeNumber == null && line.contains(MetadataName.CHANGE_NUMBER)) {
|
||||
if (metadata.changeNumber == null && line.contains(MailHeader.CHANGE_NUMBER.getName())) {
|
||||
metadata.changeNumber =
|
||||
Ints.tryParse(extractFooter(toFooterWithDelimiter(MetadataName.CHANGE_NUMBER), line));
|
||||
} else if (metadata.patchSet == null && line.contains(MetadataName.PATCH_SET)) {
|
||||
Ints.tryParse(extractFooter(MailHeader.CHANGE_NUMBER.withDelimiter(), line));
|
||||
} else if (metadata.patchSet == null && line.contains(MailHeader.PATCH_SET.getName())) {
|
||||
metadata.patchSet =
|
||||
Ints.tryParse(extractFooter(toFooterWithDelimiter(MetadataName.PATCH_SET), line));
|
||||
} else if (metadata.timestamp == null && line.contains(MetadataName.TIMESTAMP)) {
|
||||
String ts = extractFooter(toFooterWithDelimiter(MetadataName.TIMESTAMP), line);
|
||||
Ints.tryParse(extractFooter(MailHeader.PATCH_SET.withDelimiter(), line));
|
||||
} else if (metadata.timestamp == null && line.contains(MailHeader.COMMENT_DATE.getName())) {
|
||||
String ts = extractFooter(MailHeader.COMMENT_DATE.withDelimiter(), line);
|
||||
try {
|
||||
metadata.timestamp = Timestamp.from(MailUtil.rfcDateformatter.parse(ts, Instant::from));
|
||||
} catch (DateTimeParseException e) {
|
||||
log.error("Mail: Error while parsing timestamp from footer of message " + m.id(), e);
|
||||
}
|
||||
} else if (metadata.messageType == null && line.contains(MetadataName.MESSAGE_TYPE)) {
|
||||
metadata.messageType =
|
||||
extractFooter(toFooterWithDelimiter(MetadataName.MESSAGE_TYPE), line);
|
||||
} else if (metadata.messageType == null && line.contains(MailHeader.MESSAGE_TYPE.getName())) {
|
||||
metadata.messageType = extractFooter(MailHeader.MESSAGE_TYPE.withDelimiter(), line);
|
||||
}
|
||||
}
|
||||
}
|
@@ -43,6 +43,7 @@ import com.google.gerrit.server.change.EmailReviewComments;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.extensions.events.CommentAdded;
|
||||
import com.google.gerrit.server.mail.MailFilter;
|
||||
import com.google.gerrit.server.mail.send.InboundEmailRejectionSender;
|
||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||
import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
@@ -77,6 +78,7 @@ public class MailProcessor {
|
||||
private static final Logger log = LoggerFactory.getLogger(MailProcessor.class);
|
||||
|
||||
private final Emails emails;
|
||||
private final InboundEmailRejectionSender.Factory emailRejectionSender;
|
||||
private final RetryHelper retryHelper;
|
||||
private final ChangeMessagesUtil changeMessagesUtil;
|
||||
private final CommentsUtil commentsUtil;
|
||||
@@ -94,6 +96,7 @@ public class MailProcessor {
|
||||
@Inject
|
||||
public MailProcessor(
|
||||
Emails emails,
|
||||
InboundEmailRejectionSender.Factory emailRejectionSender,
|
||||
RetryHelper retryHelper,
|
||||
ChangeMessagesUtil changeMessagesUtil,
|
||||
CommentsUtil commentsUtil,
|
||||
@@ -108,6 +111,7 @@ public class MailProcessor {
|
||||
AccountCache accountCache,
|
||||
@CanonicalWebUrl Provider<String> canonicalUrl) {
|
||||
this.emails = emails;
|
||||
this.emailRejectionSender = emailRejectionSender;
|
||||
this.retryHelper = retryHelper;
|
||||
this.changeMessagesUtil = changeMessagesUtil;
|
||||
this.commentsUtil = commentsUtil;
|
||||
@@ -148,21 +152,29 @@ public class MailProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
MailMetadata metadata = MetadataParser.parse(message);
|
||||
MailMetadata metadata = MailHeaderParser.parse(message);
|
||||
|
||||
if (!metadata.hasRequiredFields()) {
|
||||
log.error(
|
||||
String.format(
|
||||
"Message %s is missing required metadata, have %s. Will delete message.",
|
||||
message.id(), metadata));
|
||||
sendRejectionEmail(message, InboundEmailRejectionSender.Error.PARSING_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Account.Id> accountIds = emails.getAccountFor(metadata.author);
|
||||
|
||||
if (accountIds.size() != 1) {
|
||||
log.error(
|
||||
String.format(
|
||||
"Address %s could not be matched to a unique account. It was matched to %s. Will delete message.",
|
||||
metadata.author, accountIds));
|
||||
|
||||
// We don't want to send an email if no accounts are linked to it.
|
||||
if (accountIds.size() > 1) {
|
||||
sendRejectionEmail(message, InboundEmailRejectionSender.Error.UNKNOWN_ACCOUNT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Account.Id accountId = accountIds.iterator().next();
|
||||
@@ -173,12 +185,23 @@ public class MailProcessor {
|
||||
}
|
||||
if (!accountState.get().getAccount().isActive()) {
|
||||
log.warn(String.format("Mail: Account %s is inactive. Will delete message.", accountId));
|
||||
sendRejectionEmail(message, InboundEmailRejectionSender.Error.INACTIVE_ACCOUNT);
|
||||
return;
|
||||
}
|
||||
|
||||
persistComments(buf, message, metadata, accountId);
|
||||
}
|
||||
|
||||
private void sendRejectionEmail(MailMessage message, InboundEmailRejectionSender.Error reason) {
|
||||
try {
|
||||
InboundEmailRejectionSender em =
|
||||
emailRejectionSender.create(message.from(), message.id(), reason);
|
||||
em.send();
|
||||
} catch (Exception e) {
|
||||
log.error("Cannot send email to warn for an error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void persistComments(
|
||||
BatchUpdate.Factory buf, MailMessage message, MailMetadata metadata, Account.Id sender)
|
||||
throws OrmException, UpdateException, RestApiException {
|
||||
@@ -190,6 +213,8 @@ public class MailProcessor {
|
||||
String.format(
|
||||
"Message %s references unique change %s, but there are %d matching changes in the index. Will delete message.",
|
||||
message.id(), metadata.changeNumber, changeDataList.size()));
|
||||
|
||||
sendRejectionEmail(message, InboundEmailRejectionSender.Error.INTERNAL_EXCEPTION);
|
||||
return;
|
||||
}
|
||||
ChangeData cd = changeDataList.get(0);
|
||||
@@ -221,6 +246,7 @@ public class MailProcessor {
|
||||
log.warn(
|
||||
String.format(
|
||||
"Could not parse any comments from %s. Will delete message.", message.id()));
|
||||
sendRejectionEmail(message, InboundEmailRejectionSender.Error.PARSING_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -30,6 +30,7 @@ import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.StarredChangesUtil;
|
||||
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
|
||||
import com.google.gerrit.server.notedb.ReviewerStateInternal;
|
||||
import com.google.gerrit.server.patch.PatchList;
|
||||
@@ -56,6 +57,7 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import org.apache.james.mime4j.dom.field.FieldName;
|
||||
import org.eclipse.jgit.diff.DiffFormatter;
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
@@ -158,7 +160,7 @@ public abstract class ChangeEmail extends NotificationEmail {
|
||||
}
|
||||
|
||||
if (patchSet != null) {
|
||||
setHeader("X-Gerrit-PatchSet", patchSet.getPatchSetId() + "");
|
||||
setHeader(MailHeader.PATCH_SET.fieldName(), patchSet.getPatchSetId() + "");
|
||||
if (patchSetInfo == null) {
|
||||
try {
|
||||
patchSetInfo =
|
||||
@@ -178,11 +180,11 @@ public abstract class ChangeEmail extends NotificationEmail {
|
||||
|
||||
super.init();
|
||||
if (timestamp != null) {
|
||||
setHeader("Date", new Date(timestamp.getTime()));
|
||||
setHeader(FieldName.DATE, new Date(timestamp.getTime()));
|
||||
}
|
||||
setChangeSubjectHeader();
|
||||
setHeader("X-Gerrit-Change-Id", "" + change.getKey().get());
|
||||
setHeader("X-Gerrit-Change-Number", "" + change.getChangeId());
|
||||
setHeader(MailHeader.CHANGE_ID.fieldName(), "" + change.getKey().get());
|
||||
setHeader(MailHeader.CHANGE_NUMBER.fieldName(), "" + change.getChangeId());
|
||||
setChangeUrlHeader();
|
||||
setCommitIdHeader();
|
||||
|
||||
@@ -202,7 +204,7 @@ public abstract class ChangeEmail extends NotificationEmail {
|
||||
private void setChangeUrlHeader() {
|
||||
final String u = getChangeUrl();
|
||||
if (u != null) {
|
||||
setHeader("X-Gerrit-ChangeURL", "<" + u + ">");
|
||||
setHeader(MailHeader.CHANGE_URL.fieldName(), "<" + u + ">");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,12 +213,12 @@ public abstract class ChangeEmail extends NotificationEmail {
|
||||
&& patchSet.getRevision() != null
|
||||
&& patchSet.getRevision().get() != null
|
||||
&& patchSet.getRevision().get().length() > 0) {
|
||||
setHeader("X-Gerrit-Commit", patchSet.getRevision().get());
|
||||
setHeader(MailHeader.COMMIT.fieldName(), patchSet.getRevision().get());
|
||||
}
|
||||
}
|
||||
|
||||
private void setChangeSubjectHeader() {
|
||||
setHeader("Subject", textTemplate("ChangeSubject"));
|
||||
setHeader(FieldName.SUBJECT, textTemplate("ChangeSubject"));
|
||||
}
|
||||
|
||||
/** Get a link to the change; null if the server doesn't know its own address. */
|
||||
@@ -481,19 +483,18 @@ public abstract class ChangeEmail extends NotificationEmail {
|
||||
patchSetInfoData.put("authorEmail", patchSetInfo.getAuthor().getEmail());
|
||||
soyContext.put("patchSetInfo", patchSetInfoData);
|
||||
|
||||
footers.add("Gerrit-MessageType: " + messageClass);
|
||||
footers.add("Gerrit-Change-Id: " + change.getKey().get());
|
||||
footers.add("Gerrit-Change-Number: " + Integer.toString(change.getChangeId()));
|
||||
footers.add("Gerrit-PatchSet: " + patchSet.getPatchSetId());
|
||||
footers.add("Gerrit-Owner: " + getNameEmailFor(change.getOwner()));
|
||||
footers.add(MailHeader.CHANGE_ID.withDelimiter() + change.getKey().get());
|
||||
footers.add(MailHeader.CHANGE_NUMBER.withDelimiter() + Integer.toString(change.getChangeId()));
|
||||
footers.add(MailHeader.PATCH_SET.withDelimiter() + patchSet.getPatchSetId());
|
||||
footers.add(MailHeader.OWNER.withDelimiter() + getNameEmailFor(change.getOwner()));
|
||||
if (change.getAssignee() != null) {
|
||||
footers.add("Gerrit-Assignee: " + getNameEmailFor(change.getAssignee()));
|
||||
footers.add(MailHeader.ASSIGNEE.withDelimiter() + getNameEmailFor(change.getAssignee()));
|
||||
}
|
||||
for (String reviewer : getEmailsByState(ReviewerStateInternal.REVIEWER)) {
|
||||
footers.add("Gerrit-Reviewer: " + reviewer);
|
||||
footers.add(MailHeader.REVIEWER.withDelimiter() + reviewer);
|
||||
}
|
||||
for (String reviewer : getEmailsByState(ReviewerStateInternal.CC)) {
|
||||
footers.add("Gerrit-CC: " + reviewer);
|
||||
footers.add(MailHeader.CC.withDelimiter() + reviewer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -31,6 +31,7 @@ import com.google.gerrit.reviewdb.client.RobotComment;
|
||||
import com.google.gerrit.server.CommentsUtil;
|
||||
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import com.google.gerrit.server.mail.MailUtil;
|
||||
import com.google.gerrit.server.mail.receive.Protocol;
|
||||
import com.google.gerrit.server.patch.PatchFile;
|
||||
@@ -54,6 +55,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.apache.james.mime4j.dom.field.FieldName;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.slf4j.Logger;
|
||||
@@ -163,14 +165,14 @@ public class CommentSender extends ReplyToChangeSender {
|
||||
|
||||
// Add header that enables identifying comments on parsed email.
|
||||
// Grouping is currently done by timestamp.
|
||||
setHeader("X-Gerrit-Comment-Date", timestamp);
|
||||
setHeader(MailHeader.COMMENT_DATE.fieldName(), timestamp);
|
||||
|
||||
if (incomingEmailEnabled) {
|
||||
if (replyToAddress == null) {
|
||||
// Remove Reply-To and use outbound SMTP (default) instead.
|
||||
removeHeader("Reply-To");
|
||||
removeHeader(FieldName.REPLY_TO);
|
||||
} else {
|
||||
setHeader("Reply-To", replyToAddress);
|
||||
setHeader(FieldName.REPLY_TO, replyToAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,12 +525,12 @@ public class CommentSender extends ReplyToChangeSender {
|
||||
soyContext.put(
|
||||
"coverLetterBlocks", commentBlocksToSoyData(CommentFormatter.parse(getCoverLetter())));
|
||||
|
||||
footers.add("Gerrit-Comment-Date: " + getCommentTimestamp());
|
||||
footers.add("Gerrit-HasComments: " + (hasComments ? "Yes" : "No"));
|
||||
footers.add("Gerrit-HasLabels: " + (labels.isEmpty() ? "No" : "Yes"));
|
||||
footers.add(MailHeader.COMMENT_DATE.withDelimiter() + getCommentTimestamp());
|
||||
footers.add(MailHeader.HAS_COMMENTS.withDelimiter() + (hasComments ? "Yes" : "No"));
|
||||
footers.add(MailHeader.HAS_LABELS.withDelimiter() + (labels.isEmpty() ? "No" : "Yes"));
|
||||
|
||||
for (Account.Id account : getReplyAccounts()) {
|
||||
footers.add("Gerrit-Comment-In-Reply-To: " + getNameEmailFor(account));
|
||||
footers.add(MailHeader.COMMENT_IN_REPLY_TO.withDelimiter() + getNameEmailFor(account));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,94 @@
|
||||
// Copyright (C) 2018 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail.send;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.extensions.api.changes.RecipientType;
|
||||
import com.google.gerrit.server.mail.Address;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
/** Send an email to inform users that parsing their inbound email failed. */
|
||||
public class InboundEmailRejectionSender extends OutgoingEmail {
|
||||
|
||||
/** Used by the templating system to determine what error message should be sent */
|
||||
public enum Error {
|
||||
PARSING_ERROR,
|
||||
INACTIVE_ACCOUNT,
|
||||
UNKNOWN_ACCOUNT,
|
||||
INTERNAL_EXCEPTION;
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
InboundEmailRejectionSender create(Address to, String threadId, Error reason);
|
||||
}
|
||||
|
||||
private final Address to;
|
||||
private final Error reason;
|
||||
private final String threadId;
|
||||
|
||||
@Inject
|
||||
public InboundEmailRejectionSender(
|
||||
EmailArguments ea, @Assisted Address to, @Assisted String threadId, @Assisted Error reason)
|
||||
throws OrmException {
|
||||
super(ea, "error");
|
||||
this.to = checkNotNull(to);
|
||||
this.threadId = checkNotNull(threadId);
|
||||
this.reason = checkNotNull(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() throws EmailException {
|
||||
super.init();
|
||||
setListIdHeader();
|
||||
|
||||
add(RecipientType.TO, to);
|
||||
|
||||
if (!threadId.isEmpty()) {
|
||||
setHeader(MailHeader.REFERENCES.fieldName(), "<" + threadId + ">");
|
||||
}
|
||||
}
|
||||
|
||||
private void setListIdHeader() {
|
||||
// Set a reasonable list id so that filters can be used to sort messages
|
||||
setHeader("List-Id", "<gerrit-noreply." + getGerritHost() + ">");
|
||||
if (getSettingsUrl() != null) {
|
||||
setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void format() throws EmailException {
|
||||
appendText(textTemplate("InboundEmailRejection_" + reason.name()));
|
||||
if (useHtml()) {
|
||||
appendHtml(soyHtmlTemplate("InboundEmailRejectionHtml_" + reason.name()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupSoyContext() {
|
||||
super.setupSoyContext();
|
||||
footers.add(MailHeader.MESSAGE_TYPE.withDelimiter() + messageClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsHtml() {
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -51,6 +51,8 @@ public class MailSoyTofuProvider implements Provider<SoyTofu> {
|
||||
"DeleteReviewerHtml.soy",
|
||||
"DeleteVote.soy",
|
||||
"DeleteVoteHtml.soy",
|
||||
"InboundEmailRejection.soy",
|
||||
"InboundEmailRejectionHtml.soy",
|
||||
"Footer.soy",
|
||||
"FooterHtml.soy",
|
||||
"HeaderHtml.soy",
|
||||
@@ -58,6 +60,8 @@ public class MailSoyTofuProvider implements Provider<SoyTofu> {
|
||||
"MergedHtml.soy",
|
||||
"NewChange.soy",
|
||||
"NewChangeHtml.soy",
|
||||
"NoReplyFooter.soy",
|
||||
"NoReplyFooterHtml.soy",
|
||||
"Private.soy",
|
||||
"RegisterNewEmail.soy",
|
||||
"ReplacePatchSet.soy",
|
||||
|
@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.Branch;
|
||||
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
|
||||
import com.google.gerrit.server.mail.Address;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import java.util.HashMap;
|
||||
@@ -115,7 +116,7 @@ public abstract class NotificationEmail extends OutgoingEmail {
|
||||
branchData.put("shortName", branch.getShortName());
|
||||
soyContext.put("branch", branchData);
|
||||
|
||||
footers.add("Gerrit-Project: " + branch.getParentKey().get());
|
||||
footers.add(MailHeader.PROJECT.withDelimiter() + branch.getParentKey().get());
|
||||
footers.add("Gerrit-Branch: " + branch.getShortName());
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.UserIdentity;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.mail.Address;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import com.google.gerrit.server.mail.send.EmailHeader.AddressList;
|
||||
import com.google.gerrit.server.permissions.PermissionBackendException;
|
||||
import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
|
||||
@@ -49,6 +50,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import org.apache.james.mime4j.dom.field.FieldName;
|
||||
import org.eclipse.jgit.util.SystemReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -57,9 +59,6 @@ import org.slf4j.LoggerFactory;
|
||||
public abstract class OutgoingEmail {
|
||||
private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class);
|
||||
|
||||
private static final String HDR_TO = "To";
|
||||
private static final String HDR_CC = "CC";
|
||||
|
||||
protected String messageClass;
|
||||
private final HashSet<Account.Id> rcptTo = new HashSet<>();
|
||||
private final Map<String, EmailHeader> headers;
|
||||
@@ -163,7 +162,7 @@ public abstract class OutgoingEmail {
|
||||
// Set Reply-To only if it hasn't been set by a child class
|
||||
// Reply-To will already be populated for the message types where Gerrit supports
|
||||
// inbound email replies.
|
||||
if (!headers.containsKey("Reply-To")) {
|
||||
if (!headers.containsKey(FieldName.REPLY_TO)) {
|
||||
StringJoiner j = new StringJoiner(", ");
|
||||
if (fromId != null) {
|
||||
Address address = toAddress(fromId);
|
||||
@@ -173,7 +172,7 @@ public abstract class OutgoingEmail {
|
||||
}
|
||||
smtpRcptTo.stream().forEach(a -> j.add(a.getEmail()));
|
||||
smtpRcptToPlaintextOnly.stream().forEach(a -> j.add(a.getEmail()));
|
||||
setHeader("Reply-To", j.toString());
|
||||
setHeader(FieldName.REPLY_TO, j.toString());
|
||||
}
|
||||
|
||||
String textPart = textBody.toString();
|
||||
@@ -208,13 +207,13 @@ public abstract class OutgoingEmail {
|
||||
Map<String, EmailHeader> shallowCopy = new HashMap<>();
|
||||
shallowCopy.putAll(headers);
|
||||
// Remove To and Cc
|
||||
shallowCopy.remove(HDR_TO);
|
||||
shallowCopy.remove(HDR_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(HDR_TO, to);
|
||||
shallowCopy.put(FieldName.TO, to);
|
||||
}
|
||||
args.emailSender.send(va.smtpFromAddress, smtpRcptToPlaintextOnly, shallowCopy, va.body);
|
||||
}
|
||||
@@ -233,17 +232,19 @@ public abstract class OutgoingEmail {
|
||||
setupSoyContext();
|
||||
|
||||
smtpFromAddress = args.fromAddressGenerator.from(fromId);
|
||||
setHeader("Date", new Date());
|
||||
headers.put("From", new EmailHeader.AddressList(smtpFromAddress));
|
||||
headers.put(HDR_TO, new EmailHeader.AddressList());
|
||||
headers.put(HDR_CC, new EmailHeader.AddressList());
|
||||
setHeader("Message-ID", "");
|
||||
setHeader(FieldName.DATE, new Date());
|
||||
headers.put(FieldName.FROM, new EmailHeader.AddressList(smtpFromAddress));
|
||||
headers.put(FieldName.TO, new EmailHeader.AddressList());
|
||||
headers.put(FieldName.CC, new EmailHeader.AddressList());
|
||||
setHeader(FieldName.MESSAGE_ID, "");
|
||||
setHeader(MailHeader.AUTO_SUBMITTED.fieldName(), "auto-generated");
|
||||
|
||||
for (RecipientType recipientType : accountsToNotify.keySet()) {
|
||||
add(recipientType, accountsToNotify.get(recipientType));
|
||||
}
|
||||
|
||||
setHeader("X-Gerrit-MessageType", messageClass);
|
||||
setHeader(MailHeader.MESSAGE_TYPE.fieldName(), messageClass);
|
||||
footers.add(MailHeader.MESSAGE_TYPE.withDelimiter() + messageClass);
|
||||
textBody = new StringBuilder();
|
||||
htmlBody = new StringBuilder();
|
||||
|
||||
@@ -500,15 +501,15 @@ public abstract class OutgoingEmail {
|
||||
if (!override) {
|
||||
return;
|
||||
}
|
||||
((EmailHeader.AddressList) headers.get(HDR_TO)).remove(addr.getEmail());
|
||||
((EmailHeader.AddressList) headers.get(HDR_CC)).remove(addr.getEmail());
|
||||
((EmailHeader.AddressList) headers.get(FieldName.TO)).remove(addr.getEmail());
|
||||
((EmailHeader.AddressList) headers.get(FieldName.CC)).remove(addr.getEmail());
|
||||
}
|
||||
switch (rt) {
|
||||
case TO:
|
||||
((EmailHeader.AddressList) headers.get(HDR_TO)).add(addr);
|
||||
((EmailHeader.AddressList) headers.get(FieldName.TO)).add(addr);
|
||||
break;
|
||||
case CC:
|
||||
((EmailHeader.AddressList) headers.get(HDR_CC)).add(addr);
|
||||
((EmailHeader.AddressList) headers.get(FieldName.CC)).add(addr);
|
||||
break;
|
||||
case BCC:
|
||||
break;
|
||||
|
@@ -23,6 +23,7 @@ import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.common.errors.EmailException;
|
||||
import com.google.gerrit.server.git.WorkQueue;
|
||||
import com.google.gerrit.server.mail.Address;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import com.google.gerrit.server.mail.send.EmailHeader;
|
||||
import com.google.gerrit.server.mail.send.EmailSender;
|
||||
import com.google.inject.AbstractModule;
|
||||
@@ -148,8 +149,8 @@ public class FakeEmailSender implements EmailSender {
|
||||
}
|
||||
|
||||
public List<Message> getMessages(String changeId, String type) {
|
||||
final String idFooter = "\nGerrit-Change-Id: " + changeId + "\n";
|
||||
final String typeFooter = "\nGerrit-MessageType: " + type + "\n";
|
||||
final String idFooter = "\n" + MailHeader.CHANGE_ID.withDelimiter() + changeId + "\n";
|
||||
final String typeFooter = "\n" + MailHeader.MESSAGE_TYPE.withDelimiter() + type + "\n";
|
||||
return getMessages()
|
||||
.stream()
|
||||
.filter(in -> in.body().contains(idFooter) && in.body().contains(typeFooter))
|
||||
|
@@ -68,6 +68,9 @@ public class ListMailFilterIT extends AbstractMailIT {
|
||||
// Check that the comments from the email have NOT been persisted
|
||||
Collection<ChangeMessageInfo> messages = gApi.changes().id(changeInfo.id).get().messages;
|
||||
assertThat(messages).hasSize(2);
|
||||
|
||||
// Check that no emails were sent because of this error
|
||||
assertThat(sender.getMessages()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -23,6 +23,7 @@ import com.google.gerrit.extensions.common.CommentInfo;
|
||||
import com.google.gerrit.server.mail.MailUtil;
|
||||
import com.google.gerrit.server.mail.receive.MailMessage;
|
||||
import com.google.gerrit.server.mail.receive.MailProcessor;
|
||||
import com.google.gerrit.testing.FakeEmailSender.Message;
|
||||
import com.google.inject.Inject;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -226,4 +227,33 @@ public class MailProcessorIT extends AbstractMailIT {
|
||||
|
||||
assertNotifyTo(admin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendNotificationOnMissingMetadatas() throws Exception {
|
||||
String changeId = createChangeWithReview();
|
||||
ChangeInfo changeInfo = gApi.changes().id(changeId).get();
|
||||
List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
|
||||
assertThat(comments).hasSize(2);
|
||||
String ts = "null"; // Erroneous timestamp to be used in erroneous metadatas
|
||||
|
||||
// Build Message
|
||||
String txt =
|
||||
newPlaintextBody(
|
||||
canonicalWebUrl.get() + "#/c/" + changeInfo._number + "/1",
|
||||
"Test Message",
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
MailMessage.Builder b =
|
||||
messageBuilderWithDefaultFields()
|
||||
.from(user.emailAddress)
|
||||
.textContent(txt + textFooterForChange(changeInfo._number, ts));
|
||||
|
||||
sender.clear();
|
||||
mailProcessor.process(b.build());
|
||||
|
||||
assertNotifyTo(user);
|
||||
Message message = sender.nextMessage();
|
||||
assertThat(message.body()).contains("was unable to parse your email");
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2018 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.mail;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.gerrit.server.mail.receive.MailMessage;
|
||||
import com.google.gerrit.testing.GerritBaseTests;
|
||||
import java.time.Instant;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AutoReplyMailFilterTest extends GerritBaseTests {
|
||||
|
||||
private AutoReplyMailFilter autoReplyMailFilter = new AutoReplyMailFilter();
|
||||
|
||||
@Test
|
||||
public void acceptsHumanReply() {
|
||||
MailMessage.Builder b = createChangeAndReplyByEmail();
|
||||
assertThat(autoReplyMailFilter.shouldProcessMessage(b.build())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discardsBulk() {
|
||||
MailMessage.Builder b = createChangeAndReplyByEmail();
|
||||
b.addAdditionalHeader("Precedence: bulk");
|
||||
assertThat(autoReplyMailFilter.shouldProcessMessage(b.build())).isFalse();
|
||||
|
||||
b = createChangeAndReplyByEmail();
|
||||
b.addAdditionalHeader("Precedence: list");
|
||||
assertThat(autoReplyMailFilter.shouldProcessMessage(b.build())).isFalse();
|
||||
|
||||
b = createChangeAndReplyByEmail();
|
||||
b.addAdditionalHeader("Precedence: junk");
|
||||
assertThat(autoReplyMailFilter.shouldProcessMessage(b.build())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void discardsAutoSubmitted() {
|
||||
MailMessage.Builder b = createChangeAndReplyByEmail();
|
||||
b.addAdditionalHeader("Auto-Submitted: yes");
|
||||
assertThat(autoReplyMailFilter.shouldProcessMessage(b.build())).isFalse();
|
||||
|
||||
b = createChangeAndReplyByEmail();
|
||||
b.addAdditionalHeader("Auto-Submitted: no");
|
||||
assertThat(autoReplyMailFilter.shouldProcessMessage(b.build())).isTrue();
|
||||
}
|
||||
|
||||
private MailMessage.Builder createChangeAndReplyByEmail() {
|
||||
// Build Message
|
||||
MailMessage.Builder b = MailMessage.builder();
|
||||
b.id("some id");
|
||||
b.from(new Address("admim@example.com"));
|
||||
b.addTo(new Address("gerrit@my-company.com")); // Not evaluated
|
||||
b.subject("");
|
||||
b.dateReceived(Instant.now());
|
||||
b.textContent("I am currently out of office, please leave a code review after the beep.");
|
||||
return b;
|
||||
}
|
||||
}
|
@@ -15,18 +15,16 @@
|
||||
package com.google.gerrit.server.mail.receive;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.gerrit.server.mail.MetadataName.toFooterWithDelimiter;
|
||||
import static com.google.gerrit.server.mail.MetadataName.toHeaderWithDelimiter;
|
||||
|
||||
import com.google.gerrit.server.mail.Address;
|
||||
import com.google.gerrit.server.mail.MetadataName;
|
||||
import com.google.gerrit.server.mail.MailHeader;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
import java.time.ZoneOffset;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MetadataParserTest {
|
||||
public class MailHeaderParserTest {
|
||||
@Test
|
||||
public void parseMetadataFromHeader() {
|
||||
// This tests if the metadata parser is able to parse metadata from the
|
||||
@@ -36,16 +34,16 @@ public class MetadataParserTest {
|
||||
b.dateReceived(Instant.now());
|
||||
b.subject("");
|
||||
|
||||
b.addAdditionalHeader(toHeaderWithDelimiter(MetadataName.CHANGE_NUMBER) + "123");
|
||||
b.addAdditionalHeader(toHeaderWithDelimiter(MetadataName.PATCH_SET) + "1");
|
||||
b.addAdditionalHeader(toHeaderWithDelimiter(MetadataName.MESSAGE_TYPE) + "comment");
|
||||
b.addAdditionalHeader(MailHeader.CHANGE_NUMBER.fieldWithDelimiter() + "123");
|
||||
b.addAdditionalHeader(MailHeader.PATCH_SET.fieldWithDelimiter() + "1");
|
||||
b.addAdditionalHeader(MailHeader.MESSAGE_TYPE.fieldWithDelimiter() + "comment");
|
||||
b.addAdditionalHeader(
|
||||
toHeaderWithDelimiter(MetadataName.TIMESTAMP) + "Tue, 25 Oct 2016 02:11:35 -0700");
|
||||
MailHeader.COMMENT_DATE.fieldWithDelimiter() + "Tue, 25 Oct 2016 02:11:35 -0700");
|
||||
|
||||
Address author = new Address("Diffy", "test@gerritcodereview.com");
|
||||
b.from(author);
|
||||
|
||||
MailMetadata meta = MetadataParser.parse(b.build());
|
||||
MailMetadata meta = MailHeaderParser.parse(b.build());
|
||||
assertThat(meta.author).isEqualTo(author.getEmail());
|
||||
assertThat(meta.changeNumber).isEqualTo(123);
|
||||
assertThat(meta.patchSet).isEqualTo(1);
|
||||
@@ -67,17 +65,17 @@ public class MetadataParserTest {
|
||||
b.subject("");
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append(toFooterWithDelimiter(MetadataName.CHANGE_NUMBER) + "123\r\n");
|
||||
stringBuilder.append("> " + toFooterWithDelimiter(MetadataName.PATCH_SET) + "1\n");
|
||||
stringBuilder.append(toFooterWithDelimiter(MetadataName.MESSAGE_TYPE) + "comment\n");
|
||||
stringBuilder.append(MailHeader.CHANGE_NUMBER.withDelimiter() + "123\r\n");
|
||||
stringBuilder.append("> " + MailHeader.PATCH_SET.withDelimiter() + "1\n");
|
||||
stringBuilder.append(MailHeader.MESSAGE_TYPE.withDelimiter() + "comment\n");
|
||||
stringBuilder.append(
|
||||
toFooterWithDelimiter(MetadataName.TIMESTAMP) + "Tue, 25 Oct 2016 02:11:35 -0700\r\n");
|
||||
MailHeader.COMMENT_DATE.withDelimiter() + "Tue, 25 Oct 2016 02:11:35 -0700\r\n");
|
||||
b.textContent(stringBuilder.toString());
|
||||
|
||||
Address author = new Address("Diffy", "test@gerritcodereview.com");
|
||||
b.from(author);
|
||||
|
||||
MailMetadata meta = MetadataParser.parse(b.build());
|
||||
MailMetadata meta = MailHeaderParser.parse(b.build());
|
||||
assertThat(meta.author).isEqualTo(author.getEmail());
|
||||
assertThat(meta.changeNumber).isEqualTo(123);
|
||||
assertThat(meta.patchSet).isEqualTo(1);
|
||||
@@ -100,13 +98,12 @@ public class MetadataParserTest {
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append(
|
||||
"<div id\"someid\">" + toFooterWithDelimiter(MetadataName.CHANGE_NUMBER) + "123</div>");
|
||||
stringBuilder.append("<div>" + toFooterWithDelimiter(MetadataName.PATCH_SET) + "1</div>");
|
||||
stringBuilder.append(
|
||||
"<div>" + toFooterWithDelimiter(MetadataName.MESSAGE_TYPE) + "comment</div>");
|
||||
"<div id\"someid\">" + MailHeader.CHANGE_NUMBER.withDelimiter() + "123</div>");
|
||||
stringBuilder.append("<div>" + MailHeader.PATCH_SET.withDelimiter() + "1</div>");
|
||||
stringBuilder.append("<div>" + MailHeader.MESSAGE_TYPE.withDelimiter() + "comment</div>");
|
||||
stringBuilder.append(
|
||||
"<div>"
|
||||
+ toFooterWithDelimiter(MetadataName.TIMESTAMP)
|
||||
+ MailHeader.COMMENT_DATE.withDelimiter()
|
||||
+ "Tue, 25 Oct 2016 02:11:35 -0700"
|
||||
+ "</div>");
|
||||
b.htmlContent(stringBuilder.toString());
|
||||
@@ -114,7 +111,7 @@ public class MetadataParserTest {
|
||||
Address author = new Address("Diffy", "test@gerritcodereview.com");
|
||||
b.from(author);
|
||||
|
||||
MailMetadata meta = MetadataParser.parse(b.build());
|
||||
MailMetadata meta = MailHeaderParser.parse(b.build());
|
||||
assertThat(meta.author).isEqualTo(author.getEmail());
|
||||
assertThat(meta.changeNumber).isEqualTo(123);
|
||||
assertThat(meta.patchSet).isEqualTo(1);
|
@@ -64,8 +64,5 @@
|
||||
browser window instead.
|
||||
|
||||
{\n}
|
||||
{\n}
|
||||
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
{call .NoReplyFooter /}
|
||||
{/template}
|
||||
|
@@ -59,8 +59,5 @@
|
||||
{/if}.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
</p>
|
||||
{call .NoReplyFooterHtml /}
|
||||
{/template}
|
||||
|
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
{template .InboundEmailRejectionFooter kind="text"}
|
||||
{\n}
|
||||
{\n}
|
||||
Thus, no actions were taken by Gerrit in response to this email,
|
||||
and you should use the Gerrit website to continue.
|
||||
{\n}
|
||||
This email was sent in response to an email coming from this address.
|
||||
In case you did not send Gerrit an email, feel free to ignore this.
|
||||
{call .NoReplyFooter /}
|
||||
{/template}
|
||||
|
||||
/**
|
||||
* The .InboundEmailRejection templates will determine the contents of the email related
|
||||
* to warning users of error in inbound emails
|
||||
*/
|
||||
|
||||
{template .InboundEmailRejection_PARSING_ERROR kind="text"}
|
||||
Gerrit Code Review was unable to parse your email.{\n}
|
||||
This might be because your email did not quote Gerrit's email,
|
||||
because you are using an unsupported email client,
|
||||
or because of a bug.
|
||||
{call .InboundEmailRejectionFooter /}
|
||||
{/template}
|
||||
|
||||
{template .InboundEmailRejection_UNKNOWN_ACCOUNT kind="text"}
|
||||
Gerrit Code Review was unable to match your email to an account.{\n}
|
||||
This may happen if several accounts are linked to this email address.
|
||||
{call .InboundEmailRejectionFooter /}
|
||||
{/template}
|
||||
|
||||
{template .InboundEmailRejection_INACTIVE_ACCOUNT kind="text"}
|
||||
Your account on this Gerrit Code Review instance is marked as inactive,
|
||||
so your email has been ignored. {\n}
|
||||
If you think this is an error, please contact your Gerrit instance administrator.
|
||||
{\n}{\n}
|
||||
This email was sent in response to an email coming from this address.
|
||||
In case you did not send Gerrit an email, feel free to ignore this.
|
||||
{call .NoReplyFooter /}
|
||||
{/template}
|
||||
|
||||
{template .InboundEmailRejection_INTERNAL_EXCEPTION kind="text"}
|
||||
Gerrit Code Review encountered an internal exception and was unable to fulfil your request.
|
||||
{\n}
|
||||
This might be caused by an ongoing maintenance or a data corruption.
|
||||
{call .InboundEmailRejectionFooter /}
|
||||
{/template}
|
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
|
||||
{template .InboundEmailRejectionFooterHtml}
|
||||
<p>
|
||||
Thus, no actions were taken by Gerrit in response to this email,
|
||||
and you should use the Gerrit website to continue.
|
||||
</p>
|
||||
<p>
|
||||
In case you did not send Gerrit an email, feel free to ignore this.
|
||||
</p>
|
||||
{call .NoReplyFooterHtml /}
|
||||
{/template}
|
||||
|
||||
/**
|
||||
* The .InboundEmailRejection templates will determine the contents of the email related
|
||||
* to warning users of error in inbound emails
|
||||
*/
|
||||
|
||||
{template .InboundEmailRejectionHtml_PARSING_ERROR}
|
||||
<p>
|
||||
Gerrit Code Review was unable to parse your email.
|
||||
</p>
|
||||
<p>
|
||||
This might be because your email did not quote Gerrit's email,
|
||||
because you are using an unsupported email client,
|
||||
or because of a bug.
|
||||
</p>
|
||||
{call .InboundEmailRejectionFooterHtml /}
|
||||
{/template}
|
||||
|
||||
{template .InboundEmailRejectionHtml_UNKNOWN_ACCOUNT}
|
||||
<p>
|
||||
Gerrit Code Review was unable to match your email to an account.
|
||||
</p>
|
||||
<p>
|
||||
This may happen if several accounts are linked to this email address.
|
||||
</p>
|
||||
{call .InboundEmailRejectionFooterHtml /}
|
||||
{/template}
|
||||
|
||||
{template .InboundEmailRejectionHtml_INACTIVE_ACCOUNT}
|
||||
<p>
|
||||
Your account on this Gerrit Code Review instance is marked as inactive,
|
||||
so your email has been ignored.
|
||||
</p>
|
||||
<p>
|
||||
If you think this is an error, please contact your Gerrit instance administrator.
|
||||
</p>
|
||||
<p>
|
||||
In case you did not send Gerrit an email, feel free to ignore this.
|
||||
</p>
|
||||
{call .NoReplyFooter /}
|
||||
{/template}
|
||||
|
||||
{template .InboundEmailRejectionHtml_INTERNAL_EXCEPTION}
|
||||
<p>
|
||||
Gerrit Code Review encountered an internal exception and was unable to fulfil your request.
|
||||
</p>
|
||||
<p>
|
||||
This might be caused by an ongoing maintenance or a data corruption.
|
||||
<p>
|
||||
{call .InboundEmailRejectionFooterHtml /}
|
||||
{/template}
|
23
resources/com/google/gerrit/server/mail/NoReplyFooter.soy
Normal file
23
resources/com/google/gerrit/server/mail/NoReplyFooter.soy
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
{template .NoReplyFooter kind="text"}
|
||||
{\n}
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
{/template}
|
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.server.mail.template}
|
||||
|
||||
{template .NoReplyFooterHtml}
|
||||
<p>
|
||||
This is a send-only email address. Replies to this message will not be read
|
||||
or answered.
|
||||
</p>
|
||||
{/template}
|
Reference in New Issue
Block a user