Merge "Add MetadataParser for inbound email"
This commit is contained in:
@@ -28,6 +28,7 @@ import com.google.gwtorm.server.OrmException;
|
|||||||
import org.eclipse.jgit.revwalk.FooterKey;
|
import org.eclipse.jgit.revwalk.FooterKey;
|
||||||
import org.eclipse.jgit.revwalk.FooterLine;
|
import org.eclipse.jgit.revwalk.FooterLine;
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -35,6 +36,9 @@ import java.util.Set;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class MailUtil {
|
public class MailUtil {
|
||||||
|
public static DateTimeFormatter rfcDateformatter =
|
||||||
|
DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss ZZZ");
|
||||||
|
|
||||||
public static MailRecipients getRecipientsFromFooters(
|
public static MailRecipients getRecipientsFromFooters(
|
||||||
ReviewDb db, AccountResolver accountResolver, boolean draftPatchSet,
|
ReviewDb db, AccountResolver accountResolver, boolean draftPatchSet,
|
||||||
List<FooterLine> footerLines) throws OrmException {
|
List<FooterLine> footerLines) throws OrmException {
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
// 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_ID = "Gerrit-Change-Id";
|
||||||
|
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 + ": ";
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
// 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.receive;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
|
/** MailMetadata represents metadata parsed from inbound email. */
|
||||||
|
public class MailMetadata {
|
||||||
|
public String changeId;
|
||||||
|
public Integer patchSet;
|
||||||
|
public String author; // Author of the email
|
||||||
|
public Timestamp timestamp;
|
||||||
|
public String messageType; // we expect comment here
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasRequiredFields() {
|
||||||
|
return changeId != null && patchSet != null && author != null &&
|
||||||
|
timestamp != null && messageType != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("Change-Id", changeId)
|
||||||
|
.add("Patch-Set", patchSet)
|
||||||
|
.add("Author", author)
|
||||||
|
.add("Timestamp", timestamp)
|
||||||
|
.add("Message-Type", messageType)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,110 @@
|
|||||||
|
// 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.receive;
|
||||||
|
|
||||||
|
import static com.google.gerrit.server.mail.MetadataName.toHeaderWithDelimiter;
|
||||||
|
import static com.google.gerrit.server.mail.MetadataName.toFooterWithDelimiter;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
import com.google.gerrit.server.mail.MailUtil;
|
||||||
|
import com.google.gerrit.server.mail.MetadataName;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/** Parse metadata from inbound email */
|
||||||
|
public class MetadataParser {
|
||||||
|
public static MailMetadata parse(MailMessage m) {
|
||||||
|
MailMetadata metadata = new MailMetadata();
|
||||||
|
// Find author
|
||||||
|
metadata.author = m.from().getEmail();
|
||||||
|
|
||||||
|
// Check email headers for X-Gerrit-<Name>
|
||||||
|
for (String header : m.additionalHeaders()) {
|
||||||
|
if (header.startsWith(toHeaderWithDelimiter(MetadataName.CHANGE_ID))) {
|
||||||
|
metadata.changeId = header
|
||||||
|
.substring(toHeaderWithDelimiter(MetadataName.CHANGE_ID).length());
|
||||||
|
} else if (header.startsWith(
|
||||||
|
toHeaderWithDelimiter(MetadataName.PATCH_SET))) {
|
||||||
|
String ps = header.substring(
|
||||||
|
toHeaderWithDelimiter(MetadataName.PATCH_SET).length());
|
||||||
|
metadata.patchSet = Ints.tryParse(ps);
|
||||||
|
} else if (header.startsWith(
|
||||||
|
toHeaderWithDelimiter(MetadataName.TIMESTAMP))) {
|
||||||
|
String ts = header.substring(
|
||||||
|
toHeaderWithDelimiter(MetadataName.TIMESTAMP).length());
|
||||||
|
metadata.timestamp = Timestamp.from(
|
||||||
|
MailUtil.rfcDateformatter.parse(ts, Instant::from));
|
||||||
|
} else if (header.startsWith(
|
||||||
|
toHeaderWithDelimiter(MetadataName.MESSAGE_TYPE))) {
|
||||||
|
metadata.messageType = header.substring(
|
||||||
|
toHeaderWithDelimiter(MetadataName.MESSAGE_TYPE).length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (metadata.hasRequiredFields()) {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the required fields were not yet found, continue to parse the text
|
||||||
|
if (!Strings.isNullOrEmpty(m.textContent())) {
|
||||||
|
String[] lines = m.textContent().split("\n");
|
||||||
|
extractFooters(lines, metadata);
|
||||||
|
if (metadata.hasRequiredFields()) {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the required fields were not yet found, continue to parse the HTML
|
||||||
|
// HTML footer are contained inside a <p> tag
|
||||||
|
if (!Strings.isNullOrEmpty(m.htmlContent())) {
|
||||||
|
String[] lines = m.htmlContent().split("</p>");
|
||||||
|
extractFooters(lines, metadata);
|
||||||
|
if (metadata.hasRequiredFields()) {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void extractFooters(String[] lines, MailMetadata metadata) {
|
||||||
|
for (String line : lines) {
|
||||||
|
if (metadata.changeId == null && line.contains(MetadataName.CHANGE_ID)) {
|
||||||
|
metadata.changeId =
|
||||||
|
extractFooter(toFooterWithDelimiter(MetadataName.CHANGE_ID), line);
|
||||||
|
} else if (metadata.patchSet == null &&
|
||||||
|
line.contains(MetadataName.PATCH_SET)) {
|
||||||
|
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);
|
||||||
|
metadata.timestamp = Timestamp.from(
|
||||||
|
MailUtil.rfcDateformatter.parse(ts, Instant::from));
|
||||||
|
} else if (metadata.messageType == null &&
|
||||||
|
line.contains(MetadataName.MESSAGE_TYPE)) {
|
||||||
|
metadata.messageType = extractFooter(
|
||||||
|
toFooterWithDelimiter(MetadataName.MESSAGE_TYPE), line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractFooter(String key, String line) {
|
||||||
|
return line.substring(line.indexOf(key) + key.length(), line.length());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,121 @@
|
|||||||
|
// 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.receive;
|
||||||
|
|
||||||
|
import static com.google.gerrit.server.mail.MetadataName.toFooterWithDelimiter;
|
||||||
|
import static com.google.gerrit.server.mail.MetadataName.toHeaderWithDelimiter;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import com.google.gerrit.server.mail.Address;
|
||||||
|
import com.google.gerrit.server.mail.MetadataName;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class MetadataParserTest {
|
||||||
|
@Test
|
||||||
|
public void testParseMetadataFromHeader() {
|
||||||
|
// This tests if the metadata parser is able to parse metadata from the
|
||||||
|
// email headers of the message.
|
||||||
|
MailMessage.Builder b = MailMessage.builder();
|
||||||
|
b.id("");
|
||||||
|
b.dateReceived(new DateTime());
|
||||||
|
b.subject("");
|
||||||
|
|
||||||
|
b.addAdditionalHeader(
|
||||||
|
toHeaderWithDelimiter(MetadataName.CHANGE_ID) + "cid");
|
||||||
|
b.addAdditionalHeader(toHeaderWithDelimiter(MetadataName.PATCH_SET) + "1");
|
||||||
|
b.addAdditionalHeader(
|
||||||
|
toHeaderWithDelimiter(MetadataName.MESSAGE_TYPE) +"comment");
|
||||||
|
b.addAdditionalHeader(toHeaderWithDelimiter(MetadataName.TIMESTAMP) +
|
||||||
|
"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());
|
||||||
|
assertThat(meta.author).isEqualTo(author.getEmail());
|
||||||
|
assertThat(meta.changeId).isEqualTo("cid");
|
||||||
|
assertThat(meta.patchSet).isEqualTo(1);
|
||||||
|
assertThat(meta.messageType).isEqualTo("comment");
|
||||||
|
assertThat(meta.timestamp.getTime()).isEqualTo(
|
||||||
|
new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseMetadataFromText() {
|
||||||
|
// This tests if the metadata parser is able to parse metadata from the
|
||||||
|
// the text body of the message.
|
||||||
|
MailMessage.Builder b = MailMessage.builder();
|
||||||
|
b.id("");
|
||||||
|
b.dateReceived(new DateTime());
|
||||||
|
b.subject("");
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.append(
|
||||||
|
toFooterWithDelimiter(MetadataName.CHANGE_ID) + "cid" + "\n");
|
||||||
|
stringBuilder.append(
|
||||||
|
toFooterWithDelimiter(MetadataName.PATCH_SET) + "1" + "\n");
|
||||||
|
stringBuilder.append(
|
||||||
|
toFooterWithDelimiter(MetadataName.MESSAGE_TYPE) + "comment" + "\n");
|
||||||
|
stringBuilder.append(toFooterWithDelimiter(MetadataName.TIMESTAMP) +
|
||||||
|
"Tue, 25 Oct 2016 02:11:35 -0700" + "\n");
|
||||||
|
b.textContent(stringBuilder.toString());
|
||||||
|
|
||||||
|
Address author = new Address("Diffy", "test@gerritcodereview.com");
|
||||||
|
b.from(author);
|
||||||
|
|
||||||
|
MailMetadata meta = MetadataParser.parse(b.build());
|
||||||
|
assertThat(meta.author).isEqualTo(author.getEmail());
|
||||||
|
assertThat(meta.changeId).isEqualTo("cid");
|
||||||
|
assertThat(meta.patchSet).isEqualTo(1);
|
||||||
|
assertThat(meta.messageType).isEqualTo("comment");
|
||||||
|
assertThat(meta.timestamp.getTime()).isEqualTo(
|
||||||
|
new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseMetadataFromHTML() {
|
||||||
|
// This tests if the metadata parser is able to parse metadata from the
|
||||||
|
// the HTML body of the message.
|
||||||
|
MailMessage.Builder b = MailMessage.builder();
|
||||||
|
b.id("");
|
||||||
|
b.dateReceived(new DateTime());
|
||||||
|
b.subject("");
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
stringBuilder.append("<p>" +
|
||||||
|
toFooterWithDelimiter(MetadataName.CHANGE_ID) + "cid" + "</p>");
|
||||||
|
stringBuilder.append("<p>" + toFooterWithDelimiter(MetadataName.PATCH_SET) +
|
||||||
|
"1" + "</p>");
|
||||||
|
stringBuilder.append("<p>" +
|
||||||
|
toFooterWithDelimiter(MetadataName.MESSAGE_TYPE) + "comment" + "</p>");
|
||||||
|
stringBuilder.append("<p>" + toFooterWithDelimiter(MetadataName.TIMESTAMP) +
|
||||||
|
"Tue, 25 Oct 2016 02:11:35 -0700" + "</p>");
|
||||||
|
b.htmlContent(stringBuilder.toString());
|
||||||
|
|
||||||
|
Address author = new Address("Diffy", "test@gerritcodereview.com");
|
||||||
|
b.from(author);
|
||||||
|
|
||||||
|
MailMetadata meta = MetadataParser.parse(b.build());
|
||||||
|
assertThat(meta.author).isEqualTo(author.getEmail());
|
||||||
|
assertThat(meta.changeId).isEqualTo("cid");
|
||||||
|
assertThat(meta.patchSet).isEqualTo(1);
|
||||||
|
assertThat(meta.messageType).isEqualTo("comment");
|
||||||
|
assertThat(meta.timestamp.getTime()).isEqualTo(
|
||||||
|
new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user