summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Kundrát <jkt@flaska.net>2014-11-24 20:54:56 +0100
committerJan Kundrát <jkt@flaska.net>2014-11-25 15:56:49 +0100
commitaca0479621d21f659d1e98b65e5c32c3cadaafc9 (patch)
treeb06d28ae5e70935fa5e877f261911d0197c6e18a
parent2b2d62b1d1704b639287fc62fe87bedf2cab85bf (diff)
Fix quoted-printable encoding of e-mail addresses
When the RFC2047-style escapes are used for e-mail headers, only a limited subset of characters is allowed to be passed unescaped. This is governed by the "phrase" production in RFC 2047, section 5, case (3), see [1]. In real world, this manifested as silently truncating the "(Code Review)" part of the e-mail address when the author's name was not pure ASCII (/me waves). Both of the two most popular open source IMAP servers, Dovecot and Cyrus, interpret these Gerrit e-mails as having a RFC5322-style comment, and therefore that part effectively gets lost. GMail, on the other hand, shows this comment as a part of the From address. The change involved some refactoring of the way quoted-printable encoder works. Working on Unicode code points one at a time is easier in this context. It would be cool to delegate this altogether to some real library, but org.apache.commons.codec.net.QuotedPrintableCodec doesn't solve anything (it doesn't even bother to pretend that it talks RFC2047), and I don't think I could get away with introducing JavaMail as a dependency in a patch release. [1] https://tools.ietf.org/html/rfc2047#section-5 Change-Id: I77452728f9b55a16bc9bd65208c187c44ce43153
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java40
-rw-r--r--gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java6
2 files changed, 38 insertions, 8 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
index 6492a5e7ca..7e4f36f3be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
@@ -64,23 +64,47 @@ public abstract class EmailHeader {
return false;
}
+ static boolean needsQuotedPrintableWithinPhrase(final int cp) {
+ switch (cp) {
+ case '!':
+ case '*':
+ case '+':
+ case '-':
+ case '/':
+ case '=':
+ case '_':
+ return false;
+ default:
+ if (('a' <= cp && cp <= 'z')
+ || ('A' <= cp && cp <= 'Z')
+ || ('0' <= cp && cp <= '9')) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
static java.lang.String quotedPrintable(java.lang.String value)
throws UnsupportedEncodingException {
final StringBuilder r = new StringBuilder();
- final byte[] encoded = value.getBytes("UTF-8");
r.append("=?UTF-8?Q?");
- for (byte b : encoded) {
- if (b == ' ') {
+ for (int i = 0; i < value.length(); i++) {
+ final int cp = value.codePointAt(i);
+ if (cp == ' ') {
r.append('_');
- } else if (b == ',' || b == '=' || b == '"' || b == '_' || b < ' ' || '~' <= b) {
- r.append('=');
- r.append(Integer.toHexString((b >>> 4) & 0x0f).toUpperCase());
- r.append(Integer.toHexString(b & 0x0f).toUpperCase());
+ } else if (needsQuotedPrintableWithinPhrase(cp)) {
+ byte[] buf = new java.lang.String(Character.toChars(cp)).getBytes("UTF-8");
+ for (byte b: buf) {
+ r.append('=');
+ r.append(Integer.toHexString((b >>> 4) & 0x0f).toUpperCase());
+ r.append(Integer.toHexString(b & 0x0f).toUpperCase());
+ }
} else {
- r.append((char) b);
+ r.append(Character.toChars(cp));
}
}
r.append("?=");
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
index 02ebf51a69..625e4b6ef6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/AddressTest.java
@@ -131,6 +131,12 @@ public class AddressTest {
}
@Test
+ public void testToHeaderString_NameEmail7() {
+ assertEquals("=?UTF-8?Q?A_=E2=82=AC_B_=28Code_Review=29?= <a@a>",
+ format("A \u20ac B (Code Review)", "a@a"));
+ }
+
+ @Test
public void testToHeaderString_Email1() {
assertEquals("a@a", format(null, "a@a"));
}