PageRenderTime 56ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://github.com/SOKP-1/packages_apps_UnifiedEmail
Java | 547 lines | 313 code | 52 blank | 182 comment | 98 complexity | 0963dc336e748d2674c10fa65c9e4d07 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.mail.providers;
  17. import android.os.Parcel;
  18. import android.os.Parcelable;
  19. import android.text.TextUtils;
  20. import android.text.util.Rfc822Token;
  21. import android.text.util.Rfc822Tokenizer;
  22. import com.android.common.Rfc822Validator;
  23. import com.android.mail.utils.LogTag;
  24. import com.android.mail.utils.LogUtils;
  25. import com.android.mail.utils.Utils;
  26. import com.google.common.annotations.VisibleForTesting;
  27. import org.apache.james.mime4j.codec.EncoderUtil;
  28. import org.apache.james.mime4j.decoder.DecoderUtil;
  29. import java.util.ArrayList;
  30. import java.util.regex.Pattern;
  31. /**
  32. * This class represent email address.
  33. *
  34. * RFC822 email address may have following format.
  35. * "name" <address> (comment)
  36. * "name" <address>
  37. * name <address>
  38. * address
  39. * Name and comment part should be MIME/base64 encoded in header if necessary.
  40. *
  41. */
  42. public class Address implements Parcelable {
  43. public static final String ADDRESS_DELIMETER = ",";
  44. /**
  45. * Address part, in the form local_part@domain_part. No surrounding angle brackets.
  46. */
  47. private String mAddress;
  48. /**
  49. * Name part. No surrounding double quote, and no MIME/base64 encoding.
  50. * This must be null if Address has no name part.
  51. */
  52. private String mName;
  53. /**
  54. * When personal is set, it will return the first token of the personal
  55. * string. Otherwise, it will return the e-mail address up to the '@' sign.
  56. */
  57. private String mSimplifiedName;
  58. // Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
  59. private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
  60. // Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
  61. private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$");
  62. // Regex that matches escaped character '\\([\\"])'
  63. private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
  64. private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
  65. // delimiters are chars that do not appear in an email address, used by pack/unpack
  66. private static final char LIST_DELIMITER_EMAIL = '\1';
  67. private static final char LIST_DELIMITER_PERSONAL = '\2';
  68. private static final String LOG_TAG = LogTag.getLogTag();
  69. public Address(String name, String address) {
  70. setName(name);
  71. setAddress(address);
  72. }
  73. /**
  74. * Returns a simplified string for this e-mail address.
  75. * When a name is known, it will return the first token of that name. Otherwise, it will
  76. * return the e-mail address up to the '@' sign.
  77. */
  78. public String getSimplifiedName() {
  79. if (mSimplifiedName == null) {
  80. if (TextUtils.isEmpty(mName) && !TextUtils.isEmpty(mAddress)) {
  81. int atSign = mAddress.indexOf('@');
  82. mSimplifiedName = (atSign != -1) ? mAddress.substring(0, atSign) : "";
  83. } else if (!TextUtils.isEmpty(mName)) {
  84. // TODO: use Contacts' NameSplitter for more reliable first-name extraction
  85. int end = mName.indexOf(' ');
  86. while (end > 0 && mName.charAt(end - 1) == ',') {
  87. end--;
  88. }
  89. mSimplifiedName = (end < 1) ? mName : mName.substring(0, end);
  90. } else {
  91. LogUtils.w(LOG_TAG, "Unable to get a simplified name");
  92. mSimplifiedName = "";
  93. }
  94. }
  95. return mSimplifiedName;
  96. }
  97. public static synchronized Address getEmailAddress(String rawAddress) {
  98. if (TextUtils.isEmpty(rawAddress)) {
  99. return null;
  100. }
  101. String name, address;
  102. final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(rawAddress);
  103. if (tokens.length > 0) {
  104. final String tokenizedName = tokens[0].getName();
  105. name = tokenizedName != null ? Utils.convertHtmlToPlainText(tokenizedName.trim())
  106. .toString() : "";
  107. address = Utils.convertHtmlToPlainText(tokens[0].getAddress()).toString();
  108. } else {
  109. name = "";
  110. address = rawAddress == null ?
  111. "" : Utils.convertHtmlToPlainText(rawAddress).toString();
  112. }
  113. return new Address(name, address);
  114. }
  115. public Address(String address) {
  116. setAddress(address);
  117. }
  118. public String getAddress() {
  119. return mAddress;
  120. }
  121. public void setAddress(String address) {
  122. mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1");
  123. }
  124. /**
  125. * Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding.
  126. *
  127. * @return Name part of email address. Returns null if it is omitted.
  128. */
  129. public String getName() {
  130. return mName;
  131. }
  132. /**
  133. * Set name part from UTF-16 string. Optional surrounding double quote will be removed.
  134. * It will be also unquoted and MIME/base64 decoded.
  135. *
  136. * @param name name part of email address as UTF-16 string. Null is acceptable.
  137. */
  138. public void setName(String name) {
  139. mName = decodeAddressName(name);
  140. }
  141. /**
  142. * Decodes name from UTF-16 string. Optional surrounding double quote will be removed.
  143. * It will be also unquoted and MIME/base64 decoded.
  144. *
  145. * @param name name part of email address as UTF-16 string. Null is acceptable.
  146. */
  147. public static String decodeAddressName(String name) {
  148. if (name != null) {
  149. name = REMOVE_OPTIONAL_DQUOTE.matcher(name).replaceAll("$1");
  150. name = UNQUOTE.matcher(name).replaceAll("$1");
  151. name = DecoderUtil.decodeEncodedWords(name);
  152. if (name.length() == 0) {
  153. name = null;
  154. }
  155. }
  156. return name;
  157. }
  158. /**
  159. * This method is used to check that all the addresses that the user
  160. * entered in a list (e.g. To:) are valid, so that none is dropped.
  161. */
  162. public static boolean isAllValid(String addressList) {
  163. // This code mimics the parse() method below.
  164. // I don't know how to better avoid the code-duplication.
  165. if (addressList != null && addressList.length() > 0) {
  166. Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
  167. for (int i = 0, length = tokens.length; i < length; ++i) {
  168. Rfc822Token token = tokens[i];
  169. String address = token.getAddress();
  170. if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
  171. return false;
  172. }
  173. }
  174. }
  175. return true;
  176. }
  177. /**
  178. * Parse a comma-delimited list of addresses in RFC822 format and return an
  179. * array of Address objects.
  180. *
  181. * @param addressList Address list in comma-delimited string.
  182. * @return An array of 0 or more Addresses.
  183. */
  184. public static Address[] parse(String addressList) {
  185. if (addressList == null || addressList.length() == 0) {
  186. return EMPTY_ADDRESS_ARRAY;
  187. }
  188. Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
  189. ArrayList<Address> addresses = new ArrayList<Address>();
  190. for (int i = 0, length = tokens.length; i < length; ++i) {
  191. Rfc822Token token = tokens[i];
  192. String address = token.getAddress();
  193. if (!TextUtils.isEmpty(address)) {
  194. if (isValidAddress(address)) {
  195. String name = token.getName();
  196. if (TextUtils.isEmpty(name)) {
  197. name = null;
  198. }
  199. addresses.add(new Address(name, address));
  200. }
  201. }
  202. }
  203. return addresses.toArray(new Address[] {});
  204. }
  205. /**
  206. * Checks whether a string email address is valid.
  207. * E.g. name@domain.com is valid.
  208. */
  209. @VisibleForTesting
  210. static boolean isValidAddress(String address) {
  211. if (TextUtils.isEmpty(address)) {
  212. return false;
  213. }
  214. int index = address.indexOf("@");
  215. return index == -1 ? false : new Rfc822Validator(address.substring(0, index))
  216. .isValid(address);
  217. }
  218. @Override
  219. public boolean equals(Object o) {
  220. if (o instanceof Address) {
  221. // It seems that the spec says that the "user" part is case-sensitive,
  222. // while the domain part in case-insesitive.
  223. // So foo@yahoo.com and Foo@yahoo.com are different.
  224. // This may seem non-intuitive from the user POV, so we
  225. // may re-consider it if it creates UI trouble.
  226. // A problem case is "replyAll" sending to both
  227. // a@b.c and to A@b.c, which turn out to be the same on the server.
  228. // Leave unchanged for now (i.e. case-sensitive).
  229. return getAddress().equals(((Address) o).getAddress());
  230. }
  231. return super.equals(o);
  232. }
  233. /**
  234. * Get human readable address string.
  235. * Do not use this for email header.
  236. *
  237. * @return Human readable address string. Not quoted and not encoded.
  238. */
  239. @Override
  240. public String toString() {
  241. if (mName != null && !mName.equals(mAddress)) {
  242. if (mName.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
  243. return Utils.ensureQuotedString(mName) + " <" + mAddress + ">";
  244. } else {
  245. return mName + " <" + mAddress + ">";
  246. }
  247. } else {
  248. return mAddress;
  249. }
  250. }
  251. /**
  252. * Get human readable comma-delimited address string.
  253. *
  254. * @param addresses Address array
  255. * @return Human readable comma-delimited address string.
  256. */
  257. public static String toString(Address[] addresses) {
  258. return toString(addresses, ADDRESS_DELIMETER);
  259. }
  260. /**
  261. * Get human readable address strings joined with the specified separator.
  262. *
  263. * @param addresses Address array
  264. * @param separator Separator
  265. * @return Human readable comma-delimited address string.
  266. */
  267. public static String toString(Address[] addresses, String separator) {
  268. if (addresses == null || addresses.length == 0) {
  269. return null;
  270. }
  271. if (addresses.length == 1) {
  272. return addresses[0].toString();
  273. }
  274. StringBuffer sb = new StringBuffer(addresses[0].toString());
  275. for (int i = 1; i < addresses.length; i++) {
  276. sb.append(separator);
  277. // TODO: investigate why this .trim() is needed.
  278. sb.append(addresses[i].toString().trim());
  279. }
  280. return sb.toString();
  281. }
  282. /**
  283. * Get RFC822/MIME compatible address string.
  284. *
  285. * @return RFC822/MIME compatible address string.
  286. * It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary.
  287. */
  288. public String toHeader() {
  289. if (mName != null) {
  290. return EncoderUtil.encodeAddressDisplayName(mName) + " <" + mAddress + ">";
  291. } else {
  292. return mAddress;
  293. }
  294. }
  295. /**
  296. * Get RFC822/MIME compatible comma-delimited address string.
  297. *
  298. * @param addresses Address array
  299. * @return RFC822/MIME compatible comma-delimited address string.
  300. * it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary.
  301. */
  302. public static String toHeader(Address[] addresses) {
  303. if (addresses == null || addresses.length == 0) {
  304. return null;
  305. }
  306. if (addresses.length == 1) {
  307. return addresses[0].toHeader();
  308. }
  309. StringBuffer sb = new StringBuffer(addresses[0].toHeader());
  310. for (int i = 1; i < addresses.length; i++) {
  311. // We need space character to be able to fold line.
  312. sb.append(", ");
  313. sb.append(addresses[i].toHeader());
  314. }
  315. return sb.toString();
  316. }
  317. /**
  318. * Get Human friendly address string.
  319. *
  320. * @return the personal part of this Address, or the address part if the
  321. * personal part is not available
  322. */
  323. public String toFriendly() {
  324. if (mName != null && mName.length() > 0) {
  325. return mName;
  326. } else {
  327. return mAddress;
  328. }
  329. }
  330. /**
  331. * Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for
  332. * details on the per-address conversion).
  333. *
  334. * @param addresses Array of Address[] values
  335. * @return A comma-delimited string listing all of the addresses supplied. Null if source
  336. * was null or empty.
  337. */
  338. public static String toFriendly(Address[] addresses) {
  339. if (addresses == null || addresses.length == 0) {
  340. return null;
  341. }
  342. if (addresses.length == 1) {
  343. return addresses[0].toFriendly();
  344. }
  345. StringBuffer sb = new StringBuffer(addresses[0].toFriendly());
  346. for (int i = 1; i < addresses.length; i++) {
  347. sb.append(", ");
  348. sb.append(addresses[i].toFriendly());
  349. }
  350. return sb.toString();
  351. }
  352. /**
  353. * Returns exactly the same result as Address.toString(Address.unpack(packedList)).
  354. */
  355. public static String unpackToString(String packedList) {
  356. return toString(unpack(packedList));
  357. }
  358. /**
  359. * Returns exactly the same result as Address.pack(Address.parse(textList)).
  360. */
  361. public static String parseAndPack(String textList) {
  362. return Address.pack(Address.parse(textList));
  363. }
  364. /**
  365. * Returns null if the packedList has 0 addresses, otherwise returns the first address.
  366. * The same as Address.unpack(packedList)[0] for non-empty list.
  367. * This is an utility method that offers some performance optimization opportunities.
  368. */
  369. public static Address unpackFirst(String packedList) {
  370. Address[] array = unpack(packedList);
  371. return array.length > 0 ? array[0] : null;
  372. }
  373. /**
  374. * Convert a packed list of addresses to a form suitable for use in an RFC822 header.
  375. * This implementation is brute-force, and could be replaced with a more efficient version
  376. * if desired.
  377. */
  378. public static String packedToHeader(String packedList) {
  379. return toHeader(unpack(packedList));
  380. }
  381. /**
  382. * Unpacks an address list previously packed with pack()
  383. * @param addressList String with packed addresses as returned by pack()
  384. * @return array of addresses resulting from unpack
  385. */
  386. public static Address[] unpack(String addressList) {
  387. if (addressList == null || addressList.length() == 0) {
  388. return EMPTY_ADDRESS_ARRAY;
  389. }
  390. ArrayList<Address> addresses = new ArrayList<Address>();
  391. int length = addressList.length();
  392. int pairStartIndex = 0;
  393. int pairEndIndex = 0;
  394. /* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
  395. is used, not for every email address; i.e. not for every iteration of the while().
  396. This reduces the theoretical complexity from quadratic to linear,
  397. and provides some speed-up in practice by removing redundant scans of the string.
  398. */
  399. int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL);
  400. while (pairStartIndex < length) {
  401. pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex);
  402. if (pairEndIndex == -1) {
  403. pairEndIndex = length;
  404. }
  405. Address address;
  406. if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) {
  407. // in this case the DELIMITER_PERSONAL is in a future pair,
  408. // so don't use personal, and don't update addressEndIndex
  409. address = new Address(null, addressList.substring(pairStartIndex, pairEndIndex));
  410. } else {
  411. address = new Address(addressList.substring(addressEndIndex + 1, pairEndIndex),
  412. addressList.substring(pairStartIndex, addressEndIndex));
  413. // only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
  414. addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
  415. }
  416. addresses.add(address);
  417. pairStartIndex = pairEndIndex + 1;
  418. }
  419. return addresses.toArray(EMPTY_ADDRESS_ARRAY);
  420. }
  421. /**
  422. * Packs an address list into a String that is very quick to read
  423. * and parse. Packed lists can be unpacked with unpack().
  424. * The format is a series of packed addresses separated by LIST_DELIMITER_EMAIL.
  425. * Each address is packed as
  426. * a pair of address and personal separated by LIST_DELIMITER_PERSONAL,
  427. * where the personal and delimiter are optional.
  428. * E.g. "foo@x.com\1joe@x.com\2Joe Doe"
  429. * @param addresses Array of addresses
  430. * @return a string containing the packed addresses.
  431. */
  432. public static String pack(Address[] addresses) {
  433. // TODO: return same value for both null & empty list
  434. if (addresses == null) {
  435. return null;
  436. }
  437. final int nAddr = addresses.length;
  438. if (nAddr == 0) {
  439. return "";
  440. }
  441. // shortcut: one email with no displayName
  442. if (nAddr == 1 && addresses[0].getName() == null) {
  443. return addresses[0].getAddress();
  444. }
  445. StringBuffer sb = new StringBuffer();
  446. for (int i = 0; i < nAddr; i++) {
  447. if (i != 0) {
  448. sb.append(LIST_DELIMITER_EMAIL);
  449. }
  450. final Address address = addresses[i];
  451. sb.append(address.getAddress());
  452. final String displayName = address.getName();
  453. if (displayName != null) {
  454. sb.append(LIST_DELIMITER_PERSONAL);
  455. sb.append(displayName);
  456. }
  457. }
  458. return sb.toString();
  459. }
  460. /**
  461. * Produces the same result as pack(array), but only packs one (this) address.
  462. */
  463. public String pack() {
  464. final String address = getAddress();
  465. final String personal = getName();
  466. if (personal == null) {
  467. return address;
  468. } else {
  469. return address + LIST_DELIMITER_PERSONAL + personal;
  470. }
  471. }
  472. public static final Creator<Address> CREATOR = new Creator<Address>() {
  473. @Override
  474. public Address createFromParcel(Parcel parcel) {
  475. return new Address(parcel);
  476. }
  477. @Override
  478. public Address[] newArray(int size) {
  479. return new Address[size];
  480. }
  481. };
  482. public Address(Parcel in) {
  483. setName(in.readString());
  484. setAddress(in.readString());
  485. }
  486. @Override
  487. public int describeContents() {
  488. return 0;
  489. }
  490. @Override
  491. public void writeToParcel(Parcel out, int flags) {
  492. out.writeString(mName);
  493. out.writeString(mAddress);
  494. }
  495. }