Use quoted printable strings in outgoing email
If we are sending non-ASCII strings in the headers of an email message we should protect them using quoted printable encoding with a UTF-8 character set. Bug: issue 387 Change-Id: I19038f7568124e2ca5900222c77110e688158437 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.mail;
|
package com.google.gerrit.server.mail;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
class Address {
|
class Address {
|
||||||
static Address parse(final String in) {
|
static Address parse(final String in) {
|
||||||
final int lt = in.indexOf('<');
|
final int lt = in.indexOf('<');
|
||||||
@@ -46,10 +48,14 @@ class Address {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return toHeaderString();
|
try {
|
||||||
|
return toHeaderString();
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("Cannot encode address", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String toHeaderString() {
|
String toHeaderString() throws UnsupportedEncodingException {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
return quotedPhrase(name) + " <" + email + ">";
|
return quotedPhrase(name) + " <" + email + ">";
|
||||||
} else if (isSimple()) {
|
} else if (isSimple()) {
|
||||||
@@ -71,10 +77,14 @@ class Address {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String quotedPhrase(final String name) {
|
private static String quotedPhrase(final String name)
|
||||||
|
throws UnsupportedEncodingException {
|
||||||
|
if (EmailHeader.needsQuotedPrintable(name)) {
|
||||||
|
return EmailHeader.quotedPrintable(name);
|
||||||
|
}
|
||||||
for (int i = 0; i < name.length(); i++) {
|
for (int i = 0; i < name.length(); i++) {
|
||||||
final char c = name.charAt(i);
|
final char c = name.charAt(i);
|
||||||
if (c < ' ' || 0x7F <= c || MUST_QUOTE_NAME.indexOf(c) != -1) {
|
if (MUST_QUOTE_NAME.indexOf(c) != -1) {
|
||||||
return wrapInQuotes(name);
|
return wrapInQuotes(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package com.google.gerrit.server.mail;
|
package com.google.gerrit.server.mail;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -40,10 +41,48 @@ abstract class EmailHeader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void write(Writer w) throws IOException {
|
void write(Writer w) throws IOException {
|
||||||
w.write(value);
|
if (needsQuotedPrintable(value)) {
|
||||||
|
w.write(quotedPrintable(value));
|
||||||
|
} else {
|
||||||
|
w.write(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean needsQuotedPrintable(java.lang.String value) {
|
||||||
|
for (int i = 0; i < value.length(); i++) {
|
||||||
|
if (value.charAt(i) < ' ' || '~' < value.charAt(i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (int i = 0; i < encoded.length; i++) {
|
||||||
|
byte b = encoded[i];
|
||||||
|
if (b == ' ') {
|
||||||
|
r.append('_');
|
||||||
|
|
||||||
|
} else if (b == '=' || b == '"' || b == '_' || b < ' ' || '~' <= b) {
|
||||||
|
r.append('=');
|
||||||
|
r.append(Integer.toHexString((b >>> 4) & 0x0f).toUpperCase());
|
||||||
|
r.append(Integer.toHexString(b & 0x0f).toUpperCase());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
r.append((char) b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.append("?=");
|
||||||
|
|
||||||
|
return r.toString();
|
||||||
|
}
|
||||||
|
|
||||||
static class Date extends EmailHeader {
|
static class Date extends EmailHeader {
|
||||||
private java.util.Date value;
|
private java.util.Date value;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ package com.google.gerrit.server.mail;
|
|||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
public class AddressTest extends TestCase {
|
public class AddressTest extends TestCase {
|
||||||
public void testParse_NameEmail1() {
|
public void testParse_NameEmail1() {
|
||||||
final Address a = Address.parse("A U Thor <author@example.com>");
|
final Address a = Address.parse("A U Thor <author@example.com>");
|
||||||
@@ -106,6 +108,10 @@ public class AddressTest extends TestCase {
|
|||||||
assertEquals("\"A \\\" C\" <a@a>", format("A \" C", "a@a"));
|
assertEquals("\"A \\\" C\" <a@a>", format("A \" C", "a@a"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testToHeaderString_NameEmail6() {
|
||||||
|
assertEquals("=?UTF-8?Q?A_=E2=82=AC_B?= <a@a>", format("A \u20ac B", "a@a"));
|
||||||
|
}
|
||||||
|
|
||||||
public void testToHeaderString_Email1() {
|
public void testToHeaderString_Email1() {
|
||||||
assertEquals("a@a", format(null, "a@a"));
|
assertEquals("a@a", format(null, "a@a"));
|
||||||
}
|
}
|
||||||
@@ -115,6 +121,10 @@ public class AddressTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String format(final String name, final String email) {
|
private static String format(final String name, final String email) {
|
||||||
return new Address(name, email).toHeaderString();
|
try {
|
||||||
|
return new Address(name, email).toHeaderString();
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("Cannot encode address", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user