content: /*
  * 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 gov2.nist.javax2.sip.parser;
  import gov2.nist.core.HostNameParser;
  import gov2.nist.core.HostPort;
  import gov2.nist.core.NameValue;
  import gov2.nist.core.NameValueList;
  import gov2.nist.core.Token;
  import gov2.nist.javax2.sip.address.GenericURI;
  import gov2.nist.javax2.sip.address.SipUri;
  import gov2.nist.javax2.sip.address.TelURLImpl;
  import gov2.nist.javax2.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());
          }
      }
  
      /**
  
      **/
  }
  
  