Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.entities; 16 : 17 : import static java.nio.charset.StandardCharsets.UTF_8; 18 : 19 : import com.google.common.base.MoreObjects; 20 : import java.io.IOException; 21 : import java.io.Writer; 22 : import java.time.Instant; 23 : import java.time.ZoneId; 24 : import java.time.format.DateTimeFormatter; 25 : import java.util.ArrayList; 26 : import java.util.Collections; 27 : import java.util.List; 28 : import java.util.Locale; 29 : import java.util.Objects; 30 : 31 106 : public abstract class EmailHeader { 32 : public abstract boolean isEmpty(); 33 : 34 : public abstract void write(Writer w) throws IOException; 35 : 36 : public static class StringEmailHeader extends EmailHeader { 37 : private final String value; 38 : 39 106 : public StringEmailHeader(String v) { 40 106 : value = v; 41 106 : } 42 : 43 : public String getString() { 44 7 : return value; 45 : } 46 : 47 : @Override 48 : public boolean isEmpty() { 49 0 : return value == null || value.length() == 0; 50 : } 51 : 52 : @Override 53 : public void write(Writer w) throws IOException { 54 0 : if (needsQuotedPrintable(value)) { 55 0 : w.write(quotedPrintable(value)); 56 : } else { 57 0 : w.write(value); 58 : } 59 0 : } 60 : 61 : @Override 62 : public int hashCode() { 63 0 : return Objects.hashCode(value); 64 : } 65 : 66 : @Override 67 : public boolean equals(Object o) { 68 2 : return (o instanceof StringEmailHeader) 69 2 : && Objects.equals(value, ((StringEmailHeader) o).value); 70 : } 71 : 72 : @Override 73 : public String toString() { 74 1 : return MoreObjects.toStringHelper(this).addValue(value).toString(); 75 : } 76 : } 77 : 78 : public static boolean needsQuotedPrintable(String value) { 79 17 : for (int i = 0; i < value.length(); i++) { 80 17 : if (value.charAt(i) < ' ' || '~' < value.charAt(i)) { 81 1 : return true; 82 : } 83 : } 84 17 : return false; 85 : } 86 : 87 : static boolean needsQuotedPrintableWithinPhrase(int cp) { 88 1 : switch (cp) { 89 : case '!': 90 : case '*': 91 : case '+': 92 : case '-': 93 : case '/': 94 : case '=': 95 : case '_': 96 0 : return false; 97 : default: 98 1 : if (('a' <= cp && cp <= 'z') || ('A' <= cp && cp <= 'Z') || ('0' <= cp && cp <= '9')) { 99 1 : return false; 100 : } 101 1 : return true; 102 : } 103 : } 104 : 105 : public static String quotedPrintable(String value) { 106 1 : final StringBuilder r = new StringBuilder(); 107 : 108 1 : r.append("=?UTF-8?Q?"); 109 1 : for (int i = 0; i < value.length(); i++) { 110 1 : final int cp = value.codePointAt(i); 111 1 : if (cp == ' ') { 112 1 : r.append('_'); 113 : 114 1 : } else if (needsQuotedPrintableWithinPhrase(cp)) { 115 1 : byte[] buf = new String(Character.toChars(cp)).getBytes(UTF_8); 116 1 : for (byte b : buf) { 117 1 : r.append('='); 118 1 : r.append(Integer.toHexString((b >>> 4) & 0x0f).toUpperCase()); 119 1 : r.append(Integer.toHexString(b & 0x0f).toUpperCase()); 120 : } 121 : 122 1 : } else { 123 1 : r.append(Character.toChars(cp)); 124 : } 125 : } 126 1 : r.append("?="); 127 : 128 1 : return r.toString(); 129 : } 130 : 131 : public static class Date extends EmailHeader { 132 : private final Instant value; 133 : 134 106 : public Date(Instant v) { 135 106 : value = v; 136 106 : } 137 : 138 : public Instant getDate() { 139 0 : return value; 140 : } 141 : 142 : @Override 143 : public boolean isEmpty() { 144 0 : return value == null; 145 : } 146 : 147 : @Override 148 : public void write(Writer w) throws IOException { 149 : // Mon, 1 Jun 2009 10:49:44 +0000 150 0 : w.write( 151 0 : DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss Z") 152 0 : .withLocale(Locale.US) 153 0 : .withZone(ZoneId.of("UTC")) 154 0 : .format(value)); 155 0 : } 156 : 157 : @Override 158 : public int hashCode() { 159 0 : return Objects.hashCode(value); 160 : } 161 : 162 : @Override 163 : public boolean equals(Object o) { 164 1 : return (o instanceof Date) && Objects.equals(value, ((Date) o).value); 165 : } 166 : 167 : @Override 168 : public String toString() { 169 0 : return MoreObjects.toStringHelper(this).addValue(value).toString(); 170 : } 171 : } 172 : 173 : public static class AddressList extends EmailHeader { 174 106 : private final List<Address> list = new ArrayList<>(); 175 : 176 106 : public AddressList() {} 177 : 178 106 : public AddressList(Address addr) { 179 106 : add(addr); 180 106 : } 181 : 182 : public List<Address> getAddressList() { 183 10 : return Collections.unmodifiableList(list); 184 : } 185 : 186 : public void add(Address addr) { 187 106 : list.add(addr); 188 106 : } 189 : 190 : public void remove(String email) { 191 47 : list.removeIf(address -> address.email().equals(email)); 192 47 : } 193 : 194 : @Override 195 : public boolean isEmpty() { 196 7 : return list.isEmpty(); 197 : } 198 : 199 : @Override 200 : public void write(Writer w) throws IOException { 201 0 : int len = 8; 202 0 : boolean firstAddress = true; 203 0 : boolean needComma = false; 204 0 : for (Address addr : list) { 205 0 : String s = addr.toHeaderString(); 206 0 : if (firstAddress) { 207 0 : firstAddress = false; 208 0 : } else if (72 < len + s.length()) { 209 0 : w.write(",\r\n\t"); 210 0 : len = 8; 211 0 : needComma = false; 212 : } 213 : 214 0 : if (needComma) { 215 0 : w.write(", "); 216 : } 217 0 : w.write(s); 218 0 : len += s.length(); 219 0 : needComma = true; 220 0 : } 221 0 : } 222 : 223 : @Override 224 : public int hashCode() { 225 0 : return Objects.hashCode(list); 226 : } 227 : 228 : @Override 229 : public boolean equals(Object o) { 230 0 : return (o instanceof AddressList) && Objects.equals(list, ((AddressList) o).list); 231 : } 232 : 233 : @Override 234 : public String toString() { 235 0 : return MoreObjects.toStringHelper(this).addValue(list).toString(); 236 : } 237 : } 238 : }