diff options
Diffstat (limited to 'java/gov/nist/javax/sip/parser/URLParser.java')
-rw-r--r-- | java/gov/nist/javax/sip/parser/URLParser.java | 833 |
1 files changed, 833 insertions, 0 deletions
diff --git a/java/gov/nist/javax/sip/parser/URLParser.java b/java/gov/nist/javax/sip/parser/URLParser.java new file mode 100644 index 0000000..9e87890 --- /dev/null +++ b/java/gov/nist/javax/sip/parser/URLParser.java @@ -0,0 +1,833 @@ +/* +* 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()); + } + } + + /** + + **/ +} + |