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