PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/android/email/mail/Address.java

https://github.com/chamatea0421/android_packages_apps_Email
Java | 474 lines | 270 code | 38 blank | 166 comment | 95 complexity | d8d227eb020d229d85860d4cf9954bd2 MD5 | raw file
  1. /*
  2. * Copyright (C) 2008 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.android.email.mail;
  17. import com.android.email.Utility;
  18. import org.apache.james.mime4j.codec.EncoderUtil;
  19. import org.apache.james.mime4j.decoder.DecoderUtil;
  20. import android.text.TextUtils;
  21. import android.text.util.Rfc822Token;
  22. import android.text.util.Rfc822Tokenizer;
  23. import java.util.ArrayList;
  24. import java.util.regex.Pattern;
  25. /**
  26. * This class represent email address.
  27. *
  28. * RFC822 email address may have following format.
  29. * "name" <address> (comment)
  30. * "name" <address>
  31. * name <address>
  32. * address
  33. * Name and comment part should be MIME/base64 encoded in header if necessary.
  34. *
  35. */
  36. public class Address {
  37. /**
  38. * Address part, in the form local_part@domain_part. No surrounding angle brackets.
  39. */
  40. String mAddress;
  41. /**
  42. * Name part. No surrounding double quote, and no MIME/base64 encoding.
  43. * This must be null if Address has no name part.
  44. */
  45. String mPersonal;
  46. // Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
  47. private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
  48. // Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
  49. private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$");
  50. // Regex that matches escaped character '\\([\\"])'
  51. private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
  52. private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
  53. // delimiters are chars that do not appear in an email address, used by pack/unpack
  54. private static final char LIST_DELIMITER_EMAIL = '\1';
  55. private static final char LIST_DELIMITER_PERSONAL = '\2';
  56. public Address(String address, String personal) {
  57. setAddress(address);
  58. setPersonal(personal);
  59. }
  60. public Address(String address) {
  61. setAddress(address);
  62. }
  63. public String getAddress() {
  64. return mAddress;
  65. }
  66. public void setAddress(String address) {
  67. this.mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1");;
  68. }
  69. /**
  70. * Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding.
  71. *
  72. * @return Name part of email address. Returns null if it is omitted.
  73. */
  74. public String getPersonal() {
  75. return mPersonal;
  76. }
  77. /**
  78. * Set name part from UTF-16 string. Optional surrounding double quote will be removed.
  79. * It will be also unquoted and MIME/base64 decoded.
  80. *
  81. * @param Personal name part of email address as UTF-16 string. Null is acceptable.
  82. */
  83. public void setPersonal(String personal) {
  84. if (personal != null) {
  85. personal = REMOVE_OPTIONAL_DQUOTE.matcher(personal).replaceAll("$1");
  86. personal = UNQUOTE.matcher(personal).replaceAll("$1");
  87. personal = DecoderUtil.decodeEncodedWords(personal);
  88. if (personal.length() == 0) {
  89. personal = null;
  90. }
  91. }
  92. this.mPersonal = personal;
  93. }
  94. /**
  95. * This method is used to check that all the addresses that the user
  96. * entered in a list (e.g. To:) are valid, so that none is dropped.
  97. */
  98. public static boolean isAllValid(String addressList) {
  99. // This code mimics the parse() method below.
  100. // I don't know how to better avoid the code-duplication.
  101. if (addressList != null && addressList.length() > 0) {
  102. Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
  103. for (int i = 0, length = tokens.length; i < length; ++i) {
  104. Rfc822Token token = tokens[i];
  105. String address = token.getAddress();
  106. if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
  107. return false;
  108. }
  109. }
  110. }
  111. return true;
  112. }
  113. /**
  114. * Parse a comma-delimited list of addresses in RFC822 format and return an
  115. * array of Address objects.
  116. *
  117. * @param addressList Address list in comma-delimited string.
  118. * @return An array of 0 or more Addresses.
  119. */
  120. public static Address[] parse(String addressList) {
  121. if (addressList == null || addressList.length() == 0) {
  122. return EMPTY_ADDRESS_ARRAY;
  123. }
  124. Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
  125. ArrayList<Address> addresses = new ArrayList<Address>();
  126. for (int i = 0, length = tokens.length; i < length; ++i) {
  127. Rfc822Token token = tokens[i];
  128. String address = token.getAddress();
  129. if (!TextUtils.isEmpty(address)) {
  130. if (isValidAddress(address)) {
  131. String name = token.getName();
  132. if (TextUtils.isEmpty(name)) {
  133. name = null;
  134. }
  135. addresses.add(new Address(address, name));
  136. }
  137. }
  138. }
  139. return addresses.toArray(new Address[] {});
  140. }
  141. /**
  142. * Checks whether a string email address is valid.
  143. * E.g. name@domain.com is valid.
  144. */
  145. /* package */ static boolean isValidAddress(String address) {
  146. // Note: Some email provider may violate the standard, so here we only check that
  147. // address consists of two part that are separated by '@', and domain part contains
  148. // at least one '.'.
  149. int len = address.length();
  150. int firstAt = address.indexOf('@');
  151. int lastAt = address.lastIndexOf('@');
  152. int firstDot = address.indexOf('.', lastAt + 1);
  153. int lastDot = address.lastIndexOf('.');
  154. return firstAt > 0 && firstAt == lastAt && lastAt + 1 < firstDot
  155. && firstDot <= lastDot && lastDot < len - 1;
  156. }
  157. @Override
  158. public boolean equals(Object o) {
  159. if (o instanceof Address) {
  160. // It seems that the spec says that the "user" part is case-sensitive,
  161. // while the domain part in case-insesitive.
  162. // So foo@yahoo.com and Foo@yahoo.com are different.
  163. // This may seem non-intuitive from the user POV, so we
  164. // may re-consider it if it creates UI trouble.
  165. // A problem case is "replyAll" sending to both
  166. // a@b.c and to A@b.c, which turn out to be the same on the server.
  167. // Leave unchanged for now (i.e. case-sensitive).
  168. return getAddress().equals(((Address) o).getAddress());
  169. }
  170. return super.equals(o);
  171. }
  172. /**
  173. * Get human readable address string.
  174. * Do not use this for email header.
  175. *
  176. * @return Human readable address string. Not quoted and not encoded.
  177. */
  178. public String toString() {
  179. if (mPersonal != null) {
  180. if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
  181. return Utility.quoteString(mPersonal) + " <" + mAddress + ">";
  182. } else {
  183. return mPersonal + " <" + mAddress + ">";
  184. }
  185. } else {
  186. return mAddress;
  187. }
  188. }
  189. /**
  190. * Get human readable comma-delimited address string.
  191. *
  192. * @param addresses Address array
  193. * @return Human readable comma-delimited address string.
  194. */
  195. public static String toString(Address[] addresses) {
  196. if (addresses == null || addresses.length == 0) {
  197. return null;
  198. }
  199. if (addresses.length == 1) {
  200. return addresses[0].toString();
  201. }
  202. StringBuffer sb = new StringBuffer(addresses[0].toString());
  203. for (int i = 1; i < addresses.length; i++) {
  204. sb.append(',');
  205. sb.append(addresses[i].toString());
  206. }
  207. return sb.toString();
  208. }
  209. /**
  210. * Get RFC822/MIME compatible address string.
  211. *
  212. * @return RFC822/MIME compatible address string.
  213. * It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary.
  214. */
  215. public String toHeader() {
  216. if (mPersonal != null) {
  217. return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">";
  218. } else {
  219. return mAddress;
  220. }
  221. }
  222. /**
  223. * Get RFC822/MIME compatible comma-delimited address string.
  224. *
  225. * @param addresses Address array
  226. * @return RFC822/MIME compatible comma-delimited address string.
  227. * it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary.
  228. */
  229. public static String toHeader(Address[] addresses) {
  230. if (addresses == null || addresses.length == 0) {
  231. return null;
  232. }
  233. if (addresses.length == 1) {
  234. return addresses[0].toHeader();
  235. }
  236. StringBuffer sb = new StringBuffer(addresses[0].toHeader());
  237. for (int i = 1; i < addresses.length; i++) {
  238. // We need space character to be able to fold line.
  239. sb.append(", ");
  240. sb.append(addresses[i].toHeader());
  241. }
  242. return sb.toString();
  243. }
  244. /**
  245. * Get Human friendly address string.
  246. *
  247. * @return the personal part of this Address, or the address part if the
  248. * personal part is not available
  249. */
  250. public String toFriendly() {
  251. if (mPersonal != null && mPersonal.length() > 0) {
  252. return mPersonal;
  253. } else {
  254. return mAddress;
  255. }
  256. }
  257. /**
  258. * Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for
  259. * details on the per-address conversion).
  260. *
  261. * @param addresses Array of Address[] values
  262. * @return A comma-delimited string listing all of the addresses supplied. Null if source
  263. * was null or empty.
  264. */
  265. public static String toFriendly(Address[] addresses) {
  266. if (addresses == null || addresses.length == 0) {
  267. return null;
  268. }
  269. if (addresses.length == 1) {
  270. return addresses[0].toFriendly();
  271. }
  272. StringBuffer sb = new StringBuffer(addresses[0].toFriendly());
  273. for (int i = 1; i < addresses.length; i++) {
  274. sb.append(',');
  275. sb.append(addresses[i].toFriendly());
  276. }
  277. return sb.toString();
  278. }
  279. /**
  280. * Returns exactly the same result as Address.toString(Address.unpack(packedList)).
  281. */
  282. public static String unpackToString(String packedList) {
  283. return toString(unpack(packedList));
  284. }
  285. /**
  286. * Returns exactly the same result as Address.pack(Address.parse(textList)).
  287. */
  288. public static String parseAndPack(String textList) {
  289. return Address.pack(Address.parse(textList));
  290. }
  291. /**
  292. * Returns null if the packedList has 0 addresses, otherwise returns the first address.
  293. * The same as Address.unpack(packedList)[0] for non-empty list.
  294. * This is an utility method that offers some performance optimization opportunities.
  295. */
  296. public static Address unpackFirst(String packedList) {
  297. Address[] array = unpack(packedList);
  298. return array.length > 0 ? array[0] : null;
  299. }
  300. /**
  301. * Convert a packed list of addresses to a form suitable for use in an RFC822 header.
  302. * This implementation is brute-force, and could be replaced with a more efficient version
  303. * if desired.
  304. */
  305. public static String packedToHeader(String packedList) {
  306. return toHeader(unpack(packedList));
  307. }
  308. /**
  309. * Unpacks an address list previously packed with pack()
  310. * @param addressList String with packed addresses as returned by pack()
  311. * @return array of addresses resulting from unpack
  312. */
  313. public static Address[] unpack(String addressList) {
  314. if (addressList == null || addressList.length() == 0) {
  315. return EMPTY_ADDRESS_ARRAY;
  316. }
  317. ArrayList<Address> addresses = new ArrayList<Address>();
  318. int length = addressList.length();
  319. int pairStartIndex = 0;
  320. int pairEndIndex = 0;
  321. /* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
  322. is used, not for every email address; i.e. not for every iteration of the while().
  323. This reduces the theoretical complexity from quadratic to linear,
  324. and provides some speed-up in practice by removing redundant scans of the string.
  325. */
  326. int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL);
  327. while (pairStartIndex < length) {
  328. pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex);
  329. if (pairEndIndex == -1) {
  330. pairEndIndex = length;
  331. }
  332. Address address;
  333. if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) {
  334. // in this case the DELIMITER_PERSONAL is in a future pair,
  335. // so don't use personal, and don't update addressEndIndex
  336. address = new Address(addressList.substring(pairStartIndex, pairEndIndex), null);
  337. } else {
  338. address = new Address(addressList.substring(pairStartIndex, addressEndIndex),
  339. addressList.substring(addressEndIndex + 1, pairEndIndex));
  340. // only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
  341. addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
  342. }
  343. addresses.add(address);
  344. pairStartIndex = pairEndIndex + 1;
  345. }
  346. return addresses.toArray(EMPTY_ADDRESS_ARRAY);
  347. }
  348. /**
  349. * Packs an address list into a String that is very quick to read
  350. * and parse. Packed lists can be unpacked with unpack().
  351. * The format is a series of packed addresses separated by LIST_DELIMITER_EMAIL.
  352. * Each address is packed as
  353. * a pair of address and personal separated by LIST_DELIMITER_PERSONAL,
  354. * where the personal and delimiter are optional.
  355. * E.g. "foo@x.com\1joe@x.com\2Joe Doe"
  356. * @param addresses Array of addresses
  357. * @return a string containing the packed addresses.
  358. */
  359. public static String pack(Address[] addresses) {
  360. // TODO: return same value for both null & empty list
  361. if (addresses == null) {
  362. return null;
  363. }
  364. final int nAddr = addresses.length;
  365. if (nAddr == 0) {
  366. return "";
  367. }
  368. // shortcut: one email with no displayName
  369. if (nAddr == 1 && addresses[0].getPersonal() == null) {
  370. return addresses[0].getAddress();
  371. }
  372. StringBuffer sb = new StringBuffer();
  373. for (int i = 0; i < nAddr; i++) {
  374. if (i != 0) {
  375. sb.append(LIST_DELIMITER_EMAIL);
  376. }
  377. final Address address = addresses[i];
  378. sb.append(address.getAddress());
  379. final String displayName = address.getPersonal();
  380. if (displayName != null) {
  381. sb.append(LIST_DELIMITER_PERSONAL);
  382. sb.append(displayName);
  383. }
  384. }
  385. return sb.toString();
  386. }
  387. /**
  388. * Produces the same result as pack(array), but only packs one (this) address.
  389. */
  390. public String pack() {
  391. final String address = getAddress();
  392. final String personal = getPersonal();
  393. if (personal == null) {
  394. return address;
  395. } else {
  396. return address + LIST_DELIMITER_PERSONAL + personal;
  397. }
  398. }
  399. /**
  400. * Legacy unpack() used for reading the old data (migration),
  401. * as found in LocalStore (Donut; db version up to 24).
  402. * @See unpack()
  403. */
  404. /* package */ static Address[] legacyUnpack(String addressList) {
  405. if (addressList == null || addressList.length() == 0) {
  406. return new Address[] { };
  407. }
  408. ArrayList<Address> addresses = new ArrayList<Address>();
  409. int length = addressList.length();
  410. int pairStartIndex = 0;
  411. int pairEndIndex = 0;
  412. int addressEndIndex = 0;
  413. while (pairStartIndex < length) {
  414. pairEndIndex = addressList.indexOf(',', pairStartIndex);
  415. if (pairEndIndex == -1) {
  416. pairEndIndex = length;
  417. }
  418. addressEndIndex = addressList.indexOf(';', pairStartIndex);
  419. String address = null;
  420. String personal = null;
  421. if (addressEndIndex == -1 || addressEndIndex > pairEndIndex) {
  422. address =
  423. Utility.fastUrlDecode(addressList.substring(pairStartIndex, pairEndIndex));
  424. }
  425. else {
  426. address =
  427. Utility.fastUrlDecode(addressList.substring(pairStartIndex, addressEndIndex));
  428. personal =
  429. Utility.fastUrlDecode(addressList.substring(addressEndIndex + 1, pairEndIndex));
  430. }
  431. addresses.add(new Address(address, personal));
  432. pairStartIndex = pairEndIndex + 1;
  433. }
  434. return addresses.toArray(new Address[] { });
  435. }
  436. }