| /* |
| * Conditions Of Use |
| * |
| * This software was developed by employees of the National Institute of |
| * Standards and Technology (NIST), an agency of the Federal Government. |
| * Pursuant to title 15 Untied States Code Section 105, works of NIST |
| * employees are not subject to copyright protection in the United States |
| * and are considered to be in the public domain. As a result, a formal |
| * license is not needed to use the software. |
| * |
| * This software is provided by NIST as a service and is expressly |
| * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED |
| * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT |
| * AND DATA ACCURACY. NIST does not warrant or make any representations |
| * regarding the use of the software or the results thereof, including but |
| * not limited to the correctness, accuracy, reliability or usefulness of |
| * the software. |
| * |
| * Permission to use this software is contingent upon your acceptance |
| * of the terms of this agreement |
| * |
| * . |
| * |
| */ |
| package gov.nist.javax.sip.parser; |
| import gov.nist.core.HostNameParser; |
| import gov.nist.core.HostPort; |
| import gov.nist.core.NameValue; |
| import gov.nist.core.NameValueList; |
| import gov.nist.core.Token; |
| import gov.nist.javax.sip.address.GenericURI; |
| import gov.nist.javax.sip.address.SipUri; |
| import gov.nist.javax.sip.address.TelURLImpl; |
| import gov.nist.javax.sip.address.TelephoneNumber; |
| import java.text.ParseException; |
| |
| /** |
| * Parser For SIP and Tel URLs. Other kinds of URL's are handled by the |
| * J2SE 1.4 URL class. |
| * @version 1.2 $Revision: 1.27 $ $Date: 2009/10/22 10:27:39 $ |
| * |
| * @author M. Ranganathan <br/> |
| * |
| * |
| */ |
| public class URLParser extends Parser { |
| |
| public URLParser(String url) { |
| this.lexer = new Lexer("sip_urlLexer", url); |
| } |
| |
| // public tag added - issued by Miguel Freitas |
| public URLParser(Lexer lexer) { |
| this.lexer = lexer; |
| this.lexer.selectLexer("sip_urlLexer"); |
| } |
| protected static boolean isMark(char next) { |
| switch (next) { |
| case '-': |
| case '_': |
| case '.': |
| case '!': |
| case '~': |
| case '*': |
| case '\'': |
| case '(': |
| case ')': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| protected static boolean isUnreserved(char next) { |
| return Lexer.isAlphaDigit(next) || isMark(next); |
| } |
| |
| protected static boolean isReservedNoSlash(char next) { |
| switch (next) { |
| case ';': |
| case '?': |
| case ':': |
| case '@': |
| case '&': |
| case '+': |
| case '$': |
| case ',': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Missing '=' bug in character set - discovered by interop testing |
| // at SIPIT 13 by Bob Johnson and Scott Holben. |
| // change . to ; by Bruno Konik |
| protected static boolean isUserUnreserved(char la) { |
| switch (la) { |
| case '&': |
| case '?': |
| case '+': |
| case '$': |
| case '#': |
| case '/': |
| case ',': |
| case ';': |
| case '=': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| protected String unreserved() throws ParseException { |
| char next = lexer.lookAhead(0); |
| if (isUnreserved(next)) { |
| lexer.consume(1); |
| return String.valueOf(next); |
| } else |
| throw createParseException("unreserved"); |
| |
| } |
| |
| /** Name or value of a parameter. |
| */ |
| protected String paramNameOrValue() throws ParseException { |
| int startIdx = lexer.getPtr(); |
| while (lexer.hasMoreChars()) { |
| char next = lexer.lookAhead(0); |
| boolean isValidChar = false; |
| switch (next) { |
| case '[': |
| case ']':// JvB: fixed this one |
| case '/': |
| case ':': |
| case '&': |
| case '+': |
| case '$': |
| isValidChar = true; |
| } |
| if (isValidChar || isUnreserved(next)) { |
| lexer.consume(1); |
| } else if (isEscaped()) { |
| lexer.consume(3); |
| } else |
| break; |
| } |
| return lexer.getBuffer().substring(startIdx, lexer.getPtr()); |
| } |
| |
| private NameValue uriParam() throws ParseException { |
| if (debug) |
| dbg_enter("uriParam"); |
| try { |
| String pvalue = ""; |
| String pname = paramNameOrValue(); |
| char next = lexer.lookAhead(0); |
| boolean isFlagParam = true; |
| if (next == '=') { |
| lexer.consume(1); |
| pvalue = paramNameOrValue(); |
| isFlagParam = false; |
| } |
| if (pname.length() == 0 && |
| ( pvalue == null || |
| pvalue.length() == 0)) |
| return null; |
| else return new NameValue(pname, pvalue, isFlagParam); |
| } finally { |
| if (debug) |
| dbg_leave("uriParam"); |
| } |
| } |
| |
| protected static boolean isReserved(char next) { |
| switch (next) { |
| case ';': |
| case '/': |
| case '?': |
| case ':': |
| case '=': // Bug fix by Bruno Konik |
| case '@': |
| case '&': |
| case '+': |
| case '$': |
| case ',': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| protected String reserved() throws ParseException { |
| char next = lexer.lookAhead(0); |
| if (isReserved(next)) { |
| lexer.consume(1); |
| return new StringBuffer().append(next).toString(); |
| } else |
| throw createParseException("reserved"); |
| } |
| |
| protected boolean isEscaped() { |
| try { |
| return lexer.lookAhead(0) == '%' && |
| Lexer.isHexDigit(lexer.lookAhead(1)) && |
| Lexer.isHexDigit(lexer.lookAhead(2)); |
| } catch (Exception ex) { |
| return false; |
| } |
| } |
| |
| protected String escaped() throws ParseException { |
| if (debug) |
| dbg_enter("escaped"); |
| try { |
| StringBuffer retval = new StringBuffer(); |
| char next = lexer.lookAhead(0); |
| char next1 = lexer.lookAhead(1); |
| char next2 = lexer.lookAhead(2); |
| if (next == '%' |
| && Lexer.isHexDigit(next1) |
| && Lexer.isHexDigit(next2)) { |
| lexer.consume(3); |
| retval.append(next); |
| retval.append(next1); |
| retval.append(next2); |
| } else |
| throw createParseException("escaped"); |
| return retval.toString(); |
| } finally { |
| if (debug) |
| dbg_leave("escaped"); |
| } |
| } |
| |
| protected String mark() throws ParseException { |
| if (debug) |
| dbg_enter("mark"); |
| try { |
| char next = lexer.lookAhead(0); |
| if (isMark(next)) { |
| lexer.consume(1); |
| return new String( new char[]{next} ); |
| } else |
| throw createParseException("mark"); |
| } finally { |
| if (debug) |
| dbg_leave("mark"); |
| } |
| } |
| |
| protected String uric() { |
| if (debug) |
| dbg_enter("uric"); |
| try { |
| try { |
| char la = lexer.lookAhead(0); |
| if (isUnreserved(la)) { |
| lexer.consume(1); |
| return Lexer.charAsString(la); |
| } else if (isReserved(la)) { |
| lexer.consume(1); |
| return Lexer.charAsString(la); |
| } else if (isEscaped()) { |
| String retval = lexer.charAsString(3); |
| lexer.consume(3); |
| return retval; |
| } else |
| return null; |
| } catch (Exception ex) { |
| return null; |
| } |
| } finally { |
| if (debug) |
| dbg_leave("uric"); |
| } |
| |
| } |
| |
| protected String uricNoSlash() { |
| if (debug) |
| dbg_enter("uricNoSlash"); |
| try { |
| try { |
| char la = lexer.lookAhead(0); |
| if (isEscaped()) { |
| String retval = lexer.charAsString(3); |
| lexer.consume(3); |
| return retval; |
| } else if (isUnreserved(la)) { |
| lexer.consume(1); |
| return Lexer.charAsString(la); |
| } else if (isReservedNoSlash(la)) { |
| lexer.consume(1); |
| return Lexer.charAsString(la); |
| } else |
| return null; |
| } catch (ParseException ex) { |
| return null; |
| } |
| } finally { |
| if (debug) |
| dbg_leave("uricNoSlash"); |
| } |
| } |
| |
| protected String uricString() throws ParseException { |
| StringBuffer retval = new StringBuffer(); |
| while (true) { |
| String next = uric(); |
| if (next == null) { |
| char la = lexer.lookAhead(0); |
| // JvB: allow IPv6 addresses in generic URI strings |
| // e.g. http://[::1] |
| if ( la == '[' ) { |
| HostNameParser hnp = new HostNameParser(this.getLexer()); |
| HostPort hp = hnp.hostPort( false ); |
| retval.append(hp.toString()); |
| continue; |
| } |
| break; |
| } |
| retval.append(next); |
| } |
| return retval.toString(); |
| } |
| |
| /** |
| * Parse and return a structure for a generic URL. |
| * Note that non SIP URLs are just stored as a string (not parsed). |
| * @return URI is a URL structure for a SIP url. |
| * @throws ParseException if there was a problem parsing. |
| */ |
| public GenericURI uriReference( boolean inBrackets ) throws ParseException { |
| if (debug) |
| dbg_enter("uriReference"); |
| GenericURI retval = null; |
| Token[] tokens = lexer.peekNextToken(2); |
| Token t1 = (Token) tokens[0]; |
| Token t2 = (Token) tokens[1]; |
| try { |
| |
| if (t1.getTokenType() == TokenTypes.SIP || |
| t1.getTokenType() == TokenTypes.SIPS) { |
| if (t2.getTokenType() == ':') |
| retval = sipURL( inBrackets ); |
| else |
| throw createParseException("Expecting \':\'"); |
| } else if (t1.getTokenType() == TokenTypes.TEL) { |
| if (t2.getTokenType() == ':') { |
| retval = telURL( inBrackets ); |
| } else |
| throw createParseException("Expecting \':\'"); |
| } else { |
| String urlString = uricString(); |
| try { |
| retval = new GenericURI(urlString); |
| } catch (ParseException ex) { |
| throw createParseException(ex.getMessage()); |
| } |
| } |
| } finally { |
| if (debug) |
| dbg_leave("uriReference"); |
| } |
| return retval; |
| } |
| |
| /** |
| * Parser for the base phone number. |
| */ |
| private String base_phone_number() throws ParseException { |
| StringBuffer s = new StringBuffer(); |
| |
| if (debug) |
| dbg_enter("base_phone_number"); |
| try { |
| int lc = 0; |
| while (lexer.hasMoreChars()) { |
| char w = lexer.lookAhead(0); |
| if (Lexer.isDigit(w) |
| || w == '-' |
| || w == '.' |
| || w == '(' |
| || w == ')') { |
| lexer.consume(1); |
| s.append(w); |
| lc++; |
| } else if (lc > 0) |
| break; |
| else |
| throw createParseException("unexpected " + w); |
| } |
| return s.toString(); |
| } finally { |
| if (debug) |
| dbg_leave("base_phone_number"); |
| } |
| |
| } |
| |
| /** |
| * Parser for the local phone #. |
| */ |
| private String local_number() throws ParseException { |
| StringBuffer s = new StringBuffer(); |
| if (debug) |
| dbg_enter("local_number"); |
| try { |
| int lc = 0; |
| while (lexer.hasMoreChars()) { |
| char la = lexer.lookAhead(0); |
| if (la == '*' |
| || la == '#' |
| || la == '-' |
| || la == '.' |
| || la == '(' |
| || la == ')' |
| // JvB: allow 'A'..'F', should be uppercase |
| || Lexer.isHexDigit(la)) { |
| lexer.consume(1); |
| s.append(la); |
| lc++; |
| } else if (lc > 0) |
| break; |
| else |
| throw createParseException("unexepcted " + la); |
| } |
| return s.toString(); |
| } finally { |
| if (debug) |
| dbg_leave("local_number"); |
| } |
| |
| } |
| |
| /** |
| * Parser for telephone subscriber. |
| * |
| * @return the parsed telephone number. |
| */ |
| public final TelephoneNumber parseTelephoneNumber( boolean inBrackets ) |
| throws ParseException { |
| TelephoneNumber tn; |
| |
| if (debug) |
| dbg_enter("telephone_subscriber"); |
| lexer.selectLexer("charLexer"); |
| try { |
| char c = lexer.lookAhead(0); |
| if (c == '+') |
| tn = global_phone_number( inBrackets ); |
| else if ( |
| Lexer.isHexDigit(c)// see RFC3966 |
| || c == '#' |
| || c == '*' |
| || c == '-' |
| || c == '.' |
| || c == '(' |
| || c == ')' ) { |
| tn = local_phone_number( inBrackets ); |
| } else |
| throw createParseException("unexpected char " + c); |
| return tn; |
| } finally { |
| if (debug) |
| dbg_leave("telephone_subscriber"); |
| } |
| |
| } |
| |
| private final TelephoneNumber global_phone_number( boolean inBrackets ) throws ParseException { |
| if (debug) |
| dbg_enter("global_phone_number"); |
| try { |
| TelephoneNumber tn = new TelephoneNumber(); |
| tn.setGlobal(true); |
| NameValueList nv = null; |
| this.lexer.match(PLUS); |
| String b = base_phone_number(); |
| tn.setPhoneNumber(b); |
| if (lexer.hasMoreChars()) { |
| char tok = lexer.lookAhead(0); |
| if (tok == ';' && inBrackets) { |
| this.lexer.consume(1); |
| nv = tel_parameters(); |
| tn.setParameters(nv); |
| } |
| } |
| return tn; |
| } finally { |
| if (debug) |
| dbg_leave("global_phone_number"); |
| } |
| } |
| |
| private TelephoneNumber local_phone_number( boolean inBrackets ) throws ParseException { |
| if (debug) |
| dbg_enter("local_phone_number"); |
| TelephoneNumber tn = new TelephoneNumber(); |
| tn.setGlobal(false); |
| NameValueList nv = null; |
| String b = null; |
| try { |
| b = local_number(); |
| tn.setPhoneNumber(b); |
| if (lexer.hasMoreChars()) { |
| Token tok = this.lexer.peekNextToken(); |
| switch (tok.getTokenType()) { |
| case SEMICOLON: |
| { |
| if (inBrackets) { |
| this.lexer.consume(1); |
| nv = tel_parameters(); |
| tn.setParameters(nv); |
| } |
| break; |
| } |
| default : |
| { |
| break; |
| } |
| } |
| } |
| } finally { |
| if (debug) |
| dbg_leave("local_phone_number"); |
| } |
| return tn; |
| } |
| |
| private NameValueList tel_parameters() throws ParseException { |
| NameValueList nvList = new NameValueList(); |
| |
| // JvB: Need to handle 'phone-context' specially |
| // 'isub' (or 'ext') MUST appear first, but we accept any order here |
| NameValue nv; |
| while ( true ) { |
| String pname = paramNameOrValue(); |
| |
| // Handle 'phone-context' specially, it may start with '+' |
| if ( pname.equalsIgnoreCase("phone-context")) { |
| nv = phone_context(); |
| } else { |
| if (lexer.lookAhead(0) == '=') { |
| lexer.consume(1); |
| String value = paramNameOrValue(); |
| nv = new NameValue( pname, value, false ); |
| } else { |
| nv = new NameValue( pname, "", true );// flag param |
| } |
| } |
| nvList.set( nv ); |
| |
| if ( lexer.lookAhead(0) == ';' ) { |
| lexer.consume(1); |
| } else { |
| return nvList; |
| } |
| } |
| |
| } |
| |
| /** |
| * Parses the 'phone-context' parameter in tel: URLs |
| * @throws ParseException |
| */ |
| private NameValue phone_context() throws ParseException { |
| lexer.match('='); |
| |
| char la = lexer.lookAhead(0); |
| Object value; |
| if (la=='+') {// global-number-digits |
| lexer.consume(1);// skip '+' |
| value = "+" + base_phone_number(); |
| } else if ( Lexer.isAlphaDigit(la) ) { |
| Token t = lexer.match( Lexer.ID );// more broad than allowed |
| value = t.getTokenValue(); |
| } else { |
| throw new ParseException( "Invalid phone-context:" + la , -1 ); |
| } |
| return new NameValue( "phone-context", value, false ); |
| } |
| |
| /** |
| * Parse and return a structure for a Tel URL. |
| * @return a parsed tel url structure. |
| */ |
| public TelURLImpl telURL( boolean inBrackets ) throws ParseException { |
| lexer.match(TokenTypes.TEL); |
| lexer.match(':'); |
| TelephoneNumber tn = this.parseTelephoneNumber(inBrackets); |
| TelURLImpl telUrl = new TelURLImpl(); |
| telUrl.setTelephoneNumber(tn); |
| return telUrl; |
| |
| } |
| |
| /** |
| * Parse and return a structure for a SIP URL. |
| * @return a URL structure for a SIP url. |
| * @throws ParseException if there was a problem parsing. |
| */ |
| public SipUri sipURL( boolean inBrackets ) throws ParseException { |
| if (debug) |
| dbg_enter("sipURL"); |
| SipUri retval = new SipUri(); |
| // pmusgrave - handle sips case |
| Token nextToken = lexer.peekNextToken(); |
| int sipOrSips = TokenTypes.SIP; |
| String scheme = TokenNames.SIP; |
| if ( nextToken.getTokenType() == TokenTypes.SIPS) |
| { |
| sipOrSips = TokenTypes.SIPS; |
| scheme = TokenNames.SIPS; |
| } |
| |
| try { |
| lexer.match(sipOrSips); |
| lexer.match(':'); |
| retval.setScheme(scheme); |
| int startOfUser = lexer.markInputPosition(); |
| String userOrHost = user();// Note: user may contain ';', host may not... |
| String passOrPort = null; |
| |
| // name:password or host:port |
| if ( lexer.lookAhead() == ':' ) { |
| lexer.consume(1); |
| passOrPort = password(); |
| } |
| |
| // name@hostPort |
| if ( lexer.lookAhead() == '@' ) { |
| lexer.consume(1); |
| retval.setUser( userOrHost ); |
| if (passOrPort!=null) retval.setUserPassword( passOrPort ); |
| } else { |
| // then userOrHost was a host, backtrack just in case a ';' was eaten... |
| lexer.rewindInputPosition( startOfUser ); |
| } |
| |
| HostNameParser hnp = new HostNameParser(this.getLexer()); |
| HostPort hp = hnp.hostPort( false ); |
| retval.setHostPort(hp); |
| |
| lexer.selectLexer("charLexer"); |
| while (lexer.hasMoreChars()) { |
| // If the URI is not enclosed in brackets, parameters belong to header |
| if (lexer.lookAhead(0) != ';' || !inBrackets) |
| break; |
| lexer.consume(1); |
| NameValue parms = uriParam(); |
| if (parms != null) retval.setUriParameter(parms); |
| } |
| |
| if (lexer.hasMoreChars() && lexer.lookAhead(0) == '?') { |
| lexer.consume(1); |
| while (lexer.hasMoreChars()) { |
| NameValue parms = qheader(); |
| retval.setQHeader(parms); |
| if (lexer.hasMoreChars() && lexer.lookAhead(0) != '&') |
| break; |
| else |
| lexer.consume(1); |
| } |
| } |
| return retval; |
| } finally { |
| if (debug) |
| dbg_leave("sipURL"); |
| } |
| } |
| |
| public String peekScheme() throws ParseException { |
| Token[] tokens = lexer.peekNextToken(1); |
| if (tokens.length == 0) |
| return null; |
| String scheme = ((Token) tokens[0]).getTokenValue(); |
| return scheme; |
| } |
| |
| /** |
| * Get a name value for a given query header (ie one that comes |
| * after the ?). |
| */ |
| protected NameValue qheader() throws ParseException { |
| String name = lexer.getNextToken('='); |
| lexer.consume(1); |
| String value = hvalue(); |
| return new NameValue(name, value, false); |
| |
| } |
| |
| protected String hvalue() throws ParseException { |
| StringBuffer retval = new StringBuffer(); |
| while (lexer.hasMoreChars()) { |
| char la = lexer.lookAhead(0); |
| // Look for a character that can terminate a URL. |
| boolean isValidChar = false; |
| switch (la) { |
| case '+': |
| case '?': |
| case ':': |
| case '[': |
| case ']': |
| case '/': |
| case '$': |
| case '_': |
| case '-': |
| case '"': |
| case '!': |
| case '~': |
| case '*': |
| case '.': |
| case '(': |
| case ')': |
| isValidChar = true; |
| } |
| if (isValidChar || Lexer.isAlphaDigit(la)) { |
| lexer.consume(1); |
| retval.append(la); |
| } else if (la == '%') { |
| retval.append(escaped()); |
| } else |
| break; |
| } |
| return retval.toString(); |
| } |
| |
| /** |
| * Scan forward until you hit a terminating character for a URL. |
| * We do not handle non sip urls in this implementation. |
| * @return the string that takes us to the end of this URL (i.e. to |
| * the next delimiter). |
| */ |
| protected String urlString() throws ParseException { |
| StringBuffer retval = new StringBuffer(); |
| lexer.selectLexer("charLexer"); |
| |
| while (lexer.hasMoreChars()) { |
| char la = lexer.lookAhead(0); |
| // Look for a character that can terminate a URL. |
| if (la == ' ' |
| || la == '\t' |
| || la == '\n' |
| || la == '>' |
| || la == '<') |
| break; |
| lexer.consume(0); |
| retval.append(la); |
| } |
| return retval.toString(); |
| } |
| |
| protected String user() throws ParseException { |
| if (debug) |
| dbg_enter("user"); |
| try { |
| int startIdx = lexer.getPtr(); |
| while (lexer.hasMoreChars()) { |
| char la = lexer.lookAhead(0); |
| if (isUnreserved(la) || isUserUnreserved(la)) { |
| lexer.consume(1); |
| } else if (isEscaped()) { |
| lexer.consume(3); |
| } else |
| break; |
| } |
| return lexer.getBuffer().substring(startIdx, lexer.getPtr()); |
| } finally { |
| if (debug) |
| dbg_leave("user"); |
| } |
| |
| } |
| |
| protected String password() throws ParseException { |
| int startIdx = lexer.getPtr(); |
| while (true) { |
| char la = lexer.lookAhead(0); |
| boolean isValidChar = false; |
| switch (la) { |
| case '&': |
| case '=': |
| case '+': |
| case '$': |
| case ',': |
| isValidChar = true; |
| } |
| if (isValidChar || isUnreserved(la)) { |
| lexer.consume(1); |
| } else if (isEscaped()) { |
| lexer.consume(3); // bug reported by |
| // Jeff Haynie |
| } else |
| break; |
| |
| } |
| return lexer.getBuffer().substring(startIdx, lexer.getPtr()); |
| } |
| |
| /** |
| * Default parse method. This method just calls uriReference. |
| */ |
| public GenericURI parse() throws ParseException { |
| return uriReference( true ); |
| } |
| |
| // quick test routine for debugging type assignment |
| public static void main(String[] args) throws ParseException |
| { |
| // quick test for sips parsing |
| String[] test = { "sip:alice@example.com", |
| "sips:alice@examples.com" , |
| "sip:3Zqkv5dajqaaas0tCjCxT0xH2ZEuEMsFl0xoasip%3A%2B3519116786244%40siplab.domain.com@213.0.115.163:7070"}; |
| |
| for ( int i = 0; i < test.length; i++) |
| { |
| URLParser p = new URLParser(test[i]); |
| |
| GenericURI uri = p.parse(); |
| System.out.println("uri type returned " + uri.getClass().getName()); |
| System.out.println(test[i] + " is SipUri? " + uri.isSipURI() |
| + ">" + uri.encode()); |
| } |
| } |
| |
| /** |
| |
| **/ |
| } |
| |