PageRenderTime 84ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/james-2.2.0/src/java/org/apache/mailet/MailAddress.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 448 lines | 294 code | 24 blank | 130 comment | 73 complexity | 5abd259c0ae12295a0c39cc788863d5e MD5 | raw file
  1. /***********************************************************************
  2. * Copyright (c) 2000-2004 The Apache Software Foundation. *
  3. * All rights reserved. *
  4. * ------------------------------------------------------------------- *
  5. * Licensed under the Apache License, Version 2.0 (the "License"); you *
  6. * may not use this file except in compliance with the License. You *
  7. * may obtain a copy of the License at: *
  8. * *
  9. * http://www.apache.org/licenses/LICENSE-2.0 *
  10. * *
  11. * Unless required by applicable law or agreed to in writing, software *
  12. * distributed under the License is distributed on an "AS IS" BASIS, *
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
  14. * implied. See the License for the specific language governing *
  15. * permissions and limitations under the License. *
  16. ***********************************************************************/
  17. package org.apache.mailet;
  18. import java.util.Locale;
  19. import javax.mail.internet.InternetAddress;
  20. import javax.mail.internet.ParseException;
  21. /**
  22. * A representation of an email address.
  23. * <p>This class encapsulates functionalities to access to different
  24. * parts of an email address without dealing with its parsing.</p>
  25. *
  26. * <p>A MailAddress is an address specified in the MAIL FROM and
  27. * RCPT TO commands in SMTP sessions. These are either passed by
  28. * an external server to the mailet-compliant SMTP server, or they
  29. * are created programmatically by the mailet-compliant server to
  30. * send to another (external) SMTP server. Mailets and matchers
  31. * use the MailAddress for the purpose of evaluating the sender
  32. * and recipient(s) of a message.</p>
  33. *
  34. * <p>MailAddress parses an email address as defined in RFC 821
  35. * (SMTP) p. 30 and 31 where addresses are defined in BNF convention.
  36. * As the mailet API does not support the aged "SMTP-relayed mail"
  37. * addressing protocol, this leaves all addresses to be a <mailbox>,
  38. * as per the spec. The MailAddress's "user" is the <local-part> of
  39. * the <mailbox> and "host" is the <domain> of the mailbox.</p>
  40. *
  41. * <p>This class is a good way to validate email addresses as there are
  42. * some valid addresses which would fail with a simpler approach
  43. * to parsing address. It also removes parsing burden from
  44. * mailets and matchers that might not realize the flexibility of an
  45. * SMTP address. For instance, "serge@home"@lokitech.com is a valid
  46. * SMTP address (the quoted text serge@home is the user and
  47. * lokitech.com is the host). This means all current parsing to date
  48. * is incorrect as we just find the first @ and use that to separate
  49. * user from host.</p>
  50. *
  51. * <p>This parses an address as per the BNF specification for <mailbox>
  52. * from RFC 821 on page 30 and 31, section 4.1.2. COMMAND SYNTAX.
  53. * http://www.freesoft.org/CIE/RFC/821/15.htm</p>
  54. *
  55. * @version 1.0
  56. */
  57. public class MailAddress implements java.io.Serializable {
  58. //We hardcode the serialVersionUID so that from James 1.2 on,
  59. // MailAddress will be deserializable (so your mail doesn't get lost)
  60. public static final long serialVersionUID = 2779163542539434916L;
  61. private final static char[] SPECIAL =
  62. {'<', '>', '(', ')', '[', ']', '\\', '.', ',', ';', ':', '@', '\"'};
  63. private String user = null;
  64. private String host = null;
  65. //Used for parsing
  66. private int pos = 0;
  67. /**
  68. * <p>Construct a MailAddress parsing the provided <code>String</code> object.</p>
  69. *
  70. * <p>The <code>personal</code> variable is left empty.</p>
  71. *
  72. * @param address the email address compliant to the RFC822 format
  73. * @throws ParseException if the parse failed
  74. */
  75. public MailAddress(String address) throws ParseException {
  76. address = address.trim();
  77. StringBuffer userSB = new StringBuffer();
  78. StringBuffer hostSB = new StringBuffer();
  79. //Begin parsing
  80. //<mailbox> ::= <local-part> "@" <domain>
  81. try {
  82. //parse local-part
  83. //<local-part> ::= <dot-string> | <quoted-string>
  84. if (address.charAt(pos) == '\"') {
  85. userSB.append(parseQuotedLocalPart(address));
  86. } else {
  87. userSB.append(parseUnquotedLocalPart(address));
  88. }
  89. if (userSB.toString().length() == 0) {
  90. throw new ParseException("No local-part (user account) found at position " + (pos + 1));
  91. }
  92. //find @
  93. if (pos >= address.length() || address.charAt(pos) != '@') {
  94. throw new ParseException("Did not find @ between local-part and domain at position " + (pos + 1));
  95. }
  96. pos++;
  97. //parse domain
  98. //<domain> ::= <element> | <element> "." <domain>
  99. //<element> ::= <name> | "#" <number> | "[" <dotnum> "]"
  100. while (true) {
  101. if (address.charAt(pos) == '#') {
  102. hostSB.append(parseNumber(address));
  103. } else if (address.charAt(pos) == '[') {
  104. hostSB.append(parseDotNum(address));
  105. } else {
  106. hostSB.append(parseDomainName(address));
  107. }
  108. if (pos >= address.length()) {
  109. break;
  110. }
  111. if (address.charAt(pos) == '.') {
  112. hostSB.append('.');
  113. pos++;
  114. continue;
  115. }
  116. break;
  117. }
  118. if (hostSB.toString().length() == 0) {
  119. throw new ParseException("No domain found at position " + (pos + 1));
  120. }
  121. } catch (IndexOutOfBoundsException ioobe) {
  122. throw new ParseException("Out of data at position " + (pos + 1));
  123. }
  124. user = userSB.toString();
  125. host = hostSB.toString();
  126. }
  127. /**
  128. * Construct a MailAddress with the provided personal name and email
  129. * address.
  130. *
  131. * @param user the username or account name on the mail server
  132. * @param host the server that should accept messages for this user
  133. * @throws ParseException if the parse failed
  134. */
  135. public MailAddress(String newUser, String newHost) throws ParseException {
  136. /* NEEDS TO BE REWORKED TO VALIDATE EACH CHAR */
  137. user = newUser;
  138. host = newHost;
  139. }
  140. /**
  141. * Constructs a MailAddress from a JavaMail InternetAddress, using only the
  142. * email address portion, discarding the personal name.
  143. */
  144. public MailAddress(InternetAddress address) throws ParseException {
  145. this(address.getAddress());
  146. }
  147. /**
  148. * Return the host part.
  149. *
  150. * @return a <code>String</code> object representing the host part
  151. * of this email address. If the host is of the dotNum form
  152. * (e.g. [yyy.yyy.yyy.yyy]) then strip the braces first.
  153. */
  154. public String getHost() {
  155. if (!(host.startsWith("[") && host.endsWith("]"))) {
  156. return host;
  157. } else {
  158. return host.substring(1, host.length() -1);
  159. }
  160. }
  161. /**
  162. * Return the user part.
  163. *
  164. * @return a <code>String</code> object representing the user part
  165. * of this email address.
  166. * @throws AddressException if the parse failed
  167. */
  168. public String getUser() {
  169. return user;
  170. }
  171. public String toString() {
  172. StringBuffer addressBuffer =
  173. new StringBuffer(128)
  174. .append(user)
  175. .append("@")
  176. .append(host);
  177. return addressBuffer.toString();
  178. }
  179. public InternetAddress toInternetAddress() {
  180. try {
  181. return new InternetAddress(toString());
  182. } catch (javax.mail.internet.AddressException ae) {
  183. //impossible really
  184. return null;
  185. }
  186. }
  187. public boolean equals(Object obj) {
  188. if (obj == null) {
  189. return false;
  190. } else if (obj instanceof String) {
  191. String theString = (String)obj;
  192. return toString().equalsIgnoreCase(theString);
  193. } else if (obj instanceof MailAddress) {
  194. MailAddress addr = (MailAddress)obj;
  195. return getUser().equalsIgnoreCase(addr.getUser()) && getHost().equalsIgnoreCase(addr.getHost());
  196. }
  197. return false;
  198. }
  199. /**
  200. * Return a hashCode for this object which should be identical for addresses
  201. * which are equivalent. This is implemented by obtaining the default
  202. * hashcode of the String representation of the MailAddress. Without this
  203. * explicit definition, the default hashCode will create different hashcodes
  204. * for separate object instances.
  205. *
  206. * @return the hashcode.
  207. */
  208. public int hashCode() {
  209. return toString().toLowerCase(Locale.US).hashCode();
  210. }
  211. private String parseQuotedLocalPart(String address) throws ParseException {
  212. StringBuffer resultSB = new StringBuffer();
  213. resultSB.append('\"');
  214. pos++;
  215. //<quoted-string> ::= """ <qtext> """
  216. //<qtext> ::= "\" <x> | "\" <x> <qtext> | <q> | <q> <qtext>
  217. while (true) {
  218. if (address.charAt(pos) == '\"') {
  219. resultSB.append('\"');
  220. //end of quoted string... move forward
  221. pos++;
  222. break;
  223. }
  224. if (address.charAt(pos) == '\\') {
  225. resultSB.append('\\');
  226. pos++;
  227. //<x> ::= any one of the 128 ASCII characters (no exceptions)
  228. char x = address.charAt(pos);
  229. if (x < 0 || x > 127) {
  230. throw new ParseException("Invalid \\ syntaxed character at position " + (pos + 1));
  231. }
  232. resultSB.append(x);
  233. pos++;
  234. } else {
  235. //<q> ::= any one of the 128 ASCII characters except <CR>,
  236. //<LF>, quote ("), or backslash (\)
  237. char q = address.charAt(pos);
  238. if (q <= 0 || q == '\n' || q == '\r' || q == '\"' || q == '\\') {
  239. throw new ParseException("Unquoted local-part (user account) must be one of the 128 ASCI characters exception <CR>, <LF>, quote (\"), or backslash (\\) at position " + (pos + 1));
  240. }
  241. resultSB.append(q);
  242. pos++;
  243. }
  244. }
  245. return resultSB.toString();
  246. }
  247. private String parseUnquotedLocalPart(String address) throws ParseException {
  248. StringBuffer resultSB = new StringBuffer();
  249. //<dot-string> ::= <string> | <string> "." <dot-string>
  250. boolean lastCharDot = false;
  251. while (true) {
  252. //<string> ::= <char> | <char> <string>
  253. //<char> ::= <c> | "\" <x>
  254. if (address.charAt(pos) == '\\') {
  255. resultSB.append('\\');
  256. pos++;
  257. //<x> ::= any one of the 128 ASCII characters (no exceptions)
  258. char x = address.charAt(pos);
  259. if (x < 0 || x > 127) {
  260. throw new ParseException("Invalid \\ syntaxed character at position " + (pos + 1));
  261. }
  262. resultSB.append(x);
  263. pos++;
  264. lastCharDot = false;
  265. } else if (address.charAt(pos) == '.') {
  266. resultSB.append('.');
  267. pos++;
  268. lastCharDot = true;
  269. } else if (address.charAt(pos) == '@') {
  270. //End of local-part
  271. break;
  272. } else {
  273. //<c> ::= any one of the 128 ASCII characters, but not any
  274. // <special> or <SP>
  275. //<special> ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "."
  276. // | "," | ";" | ":" | "@" """ | the control
  277. // characters (ASCII codes 0 through 31 inclusive and
  278. // 127)
  279. //<SP> ::= the space character (ASCII code 32)
  280. char c = address.charAt(pos);
  281. if (c <= 31 || c >= 127 || c == ' ') {
  282. throw new ParseException("Invalid character in local-part (user account) at position " + (pos + 1));
  283. }
  284. for (int i = 0; i < SPECIAL.length; i++) {
  285. if (c == SPECIAL[i]) {
  286. throw new ParseException("Invalid character in local-part (user account) at position " + (pos + 1));
  287. }
  288. }
  289. resultSB.append(c);
  290. pos++;
  291. lastCharDot = false;
  292. }
  293. }
  294. if (lastCharDot) {
  295. throw new ParseException("local-part (user account) ended with a \".\", which is invalid.");
  296. }
  297. return resultSB.toString();
  298. }
  299. private String parseNumber(String address) throws ParseException {
  300. //<number> ::= <d> | <d> <number>
  301. StringBuffer resultSB = new StringBuffer();
  302. //We keep the position from the class level pos field
  303. while (true) {
  304. if (pos >= address.length()) {
  305. break;
  306. }
  307. //<d> ::= any one of the ten digits 0 through 9
  308. char d = address.charAt(pos);
  309. if (d == '.') {
  310. break;
  311. }
  312. if (d < '0' || d > '9') {
  313. throw new ParseException("In domain, did not find a number in # address at position " + (pos + 1));
  314. }
  315. resultSB.append(d);
  316. pos++;
  317. }
  318. return resultSB.toString();
  319. }
  320. private String parseDotNum(String address) throws ParseException {
  321. //throw away all irrelevant '\' they're not necessary for escaping of '.' or digits, and are illegal as part of the domain-literal
  322. while(address.indexOf("\\")>-1){
  323. address= address.substring(0,address.indexOf("\\")) + address.substring(address.indexOf("\\")+1);
  324. }
  325. StringBuffer resultSB = new StringBuffer();
  326. //we were passed the string with pos pointing the the [ char.
  327. // take the first char ([), put it in the result buffer and increment pos
  328. resultSB.append(address.charAt(pos));
  329. pos++;
  330. //<dotnum> ::= <snum> "." <snum> "." <snum> "." <snum>
  331. for (int octet = 0; octet < 4; octet++) {
  332. //<snum> ::= one, two, or three digits representing a decimal
  333. // integer value in the range 0 through 255
  334. //<d> ::= any one of the ten digits 0 through 9
  335. StringBuffer snumSB = new StringBuffer();
  336. for (int digits = 0; digits < 3; digits++) {
  337. char d = address.charAt(pos);
  338. if (d == '.') {
  339. break;
  340. }
  341. if (d == ']') {
  342. break;
  343. }
  344. if (d < '0' || d > '9') {
  345. throw new ParseException("Invalid number at position " + (pos + 1));
  346. }
  347. snumSB.append(d);
  348. pos++;
  349. }
  350. if (snumSB.toString().length() == 0) {
  351. throw new ParseException("Number not found at position " + (pos + 1));
  352. }
  353. try {
  354. int snum = Integer.parseInt(snumSB.toString());
  355. if (snum > 255) {
  356. throw new ParseException("Invalid number at position " + (pos + 1));
  357. }
  358. } catch (NumberFormatException nfe) {
  359. throw new ParseException("Invalid number at position " + (pos + 1));
  360. }
  361. resultSB.append(snumSB.toString());
  362. if (address.charAt(pos) == ']') {
  363. if (octet < 3) {
  364. throw new ParseException("End of number reached too quickly at " + (pos + 1));
  365. } else {
  366. break;
  367. }
  368. }
  369. if (address.charAt(pos) == '.') {
  370. resultSB.append('.');
  371. pos++;
  372. }
  373. }
  374. if (address.charAt(pos) != ']') {
  375. throw new ParseException("Did not find closing bracket \"]\" in domain at position " + (pos + 1));
  376. }
  377. resultSB.append(']');
  378. pos++;
  379. return resultSB.toString();
  380. }
  381. private String parseDomainName(String address) throws ParseException {
  382. StringBuffer resultSB = new StringBuffer();
  383. //<name> ::= <a> <ldh-str> <let-dig>
  384. //<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
  385. //<let-dig> ::= <a> | <d>
  386. //<let-dig-hyp> ::= <a> | <d> | "-"
  387. //<a> ::= any one of the 52 alphabetic characters A through Z
  388. // in upper case and a through z in lower case
  389. //<d> ::= any one of the ten digits 0 through 9
  390. // basically, this is a series of letters, digits, and hyphens,
  391. // but it can't start with a digit or hypthen
  392. // and can't end with a hyphen
  393. // in practice though, we should relax this as domain names can start
  394. // with digits as well as letters. So only check that doesn't start
  395. // or end with hyphen.
  396. while (true) {
  397. if (pos >= address.length()) {
  398. break;
  399. }
  400. char ch = address.charAt(pos);
  401. if ((ch >= '0' && ch <= '9') ||
  402. (ch >= 'a' && ch <= 'z') ||
  403. (ch >= 'A' && ch <= 'Z') ||
  404. (ch == '-')) {
  405. resultSB.append(ch);
  406. pos++;
  407. continue;
  408. }
  409. if (ch == '.') {
  410. break;
  411. }
  412. throw new ParseException("Invalid character at " + pos);
  413. }
  414. String result = resultSB.toString();
  415. if (result.startsWith("-") || result.endsWith("-")) {
  416. throw new ParseException("Domain name cannot begin or end with a hyphen \"-\" at position " + (pos + 1));
  417. }
  418. return result;
  419. }
  420. }