package mireka.address.parser; import static mireka.address.parser.CharClasses.*; import java.text.ParseException; import mireka.address.parser.ast.LocalPartAST; import mireka.address.parser.ast.MailboxAST; import mireka.address.parser.ast.RemotePartAST; import mireka.address.parser.base.CharClass; import mireka.address.parser.base.CharParser; import mireka.address.parser.base.CharScanner; /** * MailboxParser parses an SMTP address, see mailbox production in the RFC. * * @see <a href="http://tools.ietf.org/html/rfc5321#section-4.1.2">RFC 5321</a> */ public class MailboxParser extends CharParser { /** * Printable US-ASCII characters not including specials. Specials are * defined in RFC 5322 Internet Message Format. */ private static final CharClass ATEXT = new CharClass() { @Override public boolean isSatisfiedBy(int ch) { return ALPHA.isSatisfiedBy(ch) || DIGIT.isSatisfiedBy(ch) || ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' || ch == '+' || ch == '-' || ch == '/' || ch == '=' || ch == '?' || ch == '^' || ch == '_' || ch == '`' || ch == '{' || ch == '|' || ch == '}' || ch == '~'; } @Override public String toString() { return "atext"; }; }; /** * Within a quoted string, any ASCII graphic or space is permitted without * backslash-quoting except double-quote and the backslash itself. */ private static final CharClass QTEXT_SMTP = new CharClass() { @Override public boolean isSatisfiedBy(int ch) { return (32 <= ch && ch <= 33) || (35 <= ch && ch <= 91) || (93 <= ch && ch <= 126); } @Override public String toString() { return "qtestSMTP"; }; }; public MailboxParser(String input) { super(new CharScanner(input)); } public MailboxParser(CharScanner scanner) { super(scanner); } public MailboxAST parse() throws ParseException { MailboxAST mailboxAST = parseMailbox(); if (currentToken.ch != -1) throw currentToken .otherSyntaxException("Superfluous characters after mailbox: {0}"); return mailboxAST; } public MailboxAST parseLeft() throws ParseException { MailboxAST mailboxAST = parseMailbox(); scanner.pushBack(currentToken); return mailboxAST; } private MailboxAST parseMailbox() throws ParseException { pushPosition(); pushSpelling(); LocalPartAST localPartAST = parseLocalPart(); accept('@'); RemotePartAST remotePartAST = parseRemotePart(); return new MailboxAST(popPosition(), popSpelling(), localPartAST, remotePartAST); } private LocalPartAST parseLocalPart() throws ParseException { pushPosition(); pushSpelling(); if (ATEXT.isSatisfiedBy(currentToken.ch)) parseDotString(); else if (currentToken.ch == '"') parseQuotedString(); else throw currentToken.syntaxException("Dot-string or Quoted-string"); return new LocalPartAST(popPosition(), popSpelling()); } private void parseDotString() throws ParseException { parseAtom(); while (currentToken.ch == '.') { acceptIt(); parseAtom(); } } private void parseAtom() throws ParseException { parseAtext(); while (ATEXT.isSatisfiedBy(currentToken.ch)) parseAtext(); } private void parseAtext() throws ParseException { if (ATEXT.isSatisfiedBy(currentToken.ch)) acceptIt(); else throw currentToken.syntaxException("atext"); } private void parseQuotedString() throws ParseException { accept('"'); while (isStarterOfQContentSmtp(currentToken.ch)) parseQContentSmtp(); accept('"'); } private void parseQContentSmtp() throws ParseException { if (QTEXT_SMTP.isSatisfiedBy(currentToken.ch)) parseQtextSmtp(); else if (currentToken.ch == '\\') parseQuotedPairSmtp(); else throw currentToken.syntaxException("qtextSMTP or '\\'"); } private boolean isStarterOfQContentSmtp(int ch) { return QTEXT_SMTP.isSatisfiedBy(ch) || ch == '\\'; } private void parseQtextSmtp() throws ParseException { if (QTEXT_SMTP.isSatisfiedBy(currentToken.ch)) acceptIt(); else throw currentToken.syntaxException("qtextSMTP"); } private void parseQuotedPairSmtp() throws ParseException { accept('\\'); if (32 <= currentToken.ch && currentToken.ch <= 126) acceptIt(); else throw currentToken.syntaxException("any ASCII graphic or space"); } private RemotePartAST parseRemotePart() throws ParseException { scanner.pushBack(currentToken); RemotePartAST remotePartAST = new RemotePartParser(scanner).parseLeft(); currentToken = scanner.scan(); spelling.append(remotePartAST.spelling); return remotePartAST; } }