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:
maximeg
2018-02-16 13:20:45 +01:00
committed by David Pursehouse
parent 96251c8d71
commit 2a10edcddc
26 changed files with 645 additions and 133 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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");

View File

@@ -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);

View 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;
}
}

View 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;
}
}

View File

@@ -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 + ": ";
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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",

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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))

View File

@@ -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

View File

@@ -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");
}
}

View 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;
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;
}
}

View File

@@ -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);

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View 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}

View File

@@ -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}