/*
* Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights
* Reserved. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package gov.nist.siplite.parser;
import gov.nist.siplite.address.*;
import gov.nist.core.*;
import java.util.Vector;
/**
* Parser For SIP and Tel URLs. Other kinds of URL's are handled by the
* J2SE 1.4 URL class.
* @version JAIN-SIP-1.1
*
*
* <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
*
*/
public class URLParser extends Parser {
/** Symbols of phone digit (RFC 2806, 2.2) */
private static final String PHONE_DIGIT = "01234567890-.()";
/** Symbols of dtmf digit (RFC 2806, 2.2) */
private static final String DTMF_DIGIT = "*#ABCD";
/** Pause characters (RFC 2806, 2.2) */
private static final String PAUSE_CHAR = "pw";
/**
* Constructor with initial URL string.
* @param url initial URL
*/
public URLParser(String url) {
this.lexer = new Lexer("sip_urlLexer", url);
}
/**
* Constructor with initial lexer engine.
* @param lexer initial lexer engine
*/
URLParser(Lexer lexer) {
this.lexer = lexer;
this.lexer.selectLexer("sip_urlLexer");
}
/**
* Checks if character is punctuation mark.
* @param next character to be checked
* @return true if character is punctuation mark
*/
protected static boolean isMark(char next) {
return
next == '-' ||
next == '_' ||
next == '.' ||
next == '!' ||
next == '~' ||
next == '*' ||
next == '\''||
next == '(' ||
next == ')';
}
/**
* Checks if character is reserved.
* @param next character to be checked
* @return true if reserved character.
*/
protected static boolean isUnreserved(char next) {
return Lexer.isAlpha(next) || Lexer.isDigit(next) || isMark(next);
}
/**
* Checks if reserved character without a slash.
* @param next character to be checked
* @return true if reserved character without a slash
*/
protected static boolean isReservedNoSlash(char next) {
return
next == ';' ||
next == '?' ||
next == ':' ||
next == '@' ||
next == '&' ||
next == '+' ||
next == '$' ||
next == ',';
}
// Missing '=' CR in character set - discovered by interop testing
// at SIPIT 13 by Bob Johnson and Scott Holben.
// Replace . by;
/**
* Checks if user is unreserved.
* @param la the character to be checked
* @return true if user is not reserved
*/
protected static boolean isUserUnreserved(char la) {
return la == '&' ||
la == '?' ||
la == '+' ||
la == '$' ||
la == '#' ||
la == '/' ||
la == ',' ||
la == ';' ||
la == '=';
}
/**
* Gets the unreserved string of characters.
* @return unreserved characters
*/
protected String unreserved() throws ParseException {
char next = lexer.lookAhead(0);
if (isUnreserved(next)) {
lexer.consume(1);
return new StringBuffer().append(next).toString();
} else throw createParseException("unreserved");
}
/**
* Name or value of a parameter.
* @return parsed name or value
* @exception ParseException if a parsing error occurs
*/
protected String paramNameOrValue() throws ParseException {
StringBuffer retval = new StringBuffer();
while (lexer.hasMoreChars()) {
char next = lexer.lookAhead(0);
if (next == '[' || next == '[' || next == '/' ||
next == ':' || next == '&' || next == '+' ||
next == '$' || next == '#' || isUnreserved(next)) {
retval.append(next);
lexer.consume(1);
} else if (isEscaped()) {
String esc = lexer.charAsString(3);
lexer.consume(3);
retval.append(esc);
} else break;
}
return retval.toString();
}
/**
* Gets the URI pamaeter.
* @return the parsed URI parameter
* @exception ParseException if a parsin error occurs
*/
protected NameValue uriParam() throws ParseException {
if (debug) dbg_enter("uriParam");
try {
String pvalue = null;
String pname = paramNameOrValue();
char next = lexer.lookAhead(0);
if (next == LexerCore.EQUALS) {
lexer.consume(1);
next = lexer.lookAhead(0);
if (next == LexerCore.DOUBLEQUOTE) {
pvalue =
"\"" + lexer.quotedString() + "\""; // quoted param
} else { // unquoted parameter
pvalue = paramNameOrValue();
}
}
return new NameValue(pname, (pvalue == null) ? "" : pvalue);
} finally {
if (debug) dbg_leave("uriParam");
}
}
/**
* Checks if character is reserved.
* @param next character to be checked
* @return true if character is reserved
*/
protected static boolean isReserved(char next) {
return next == ';' ||
next == '/' ||
next == '?' ||
next == ':' ||
next == '@' ||
next == '&' ||
next == '+' ||
next == '$' ||
next == '=' ||
next == ',';
}
/**
* Gets the listof reserved characters.
* @return string of reserved characters.
*/
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");
}
/**
* Checks if current character is escaped.
* @return true if processing an escaped sequenec
*/
protected boolean isEscaped() {
try {
char next = lexer.lookAhead(0);
char next1 = lexer.lookAhead(1);
char next2 = lexer.lookAhead(2);
return (next == '%' && Lexer.isHexDigit(next1)
&& Lexer.isHexDigit(next2));
} catch (ParseException ex) {
return false;
}
}
/**
* Gets the escaped character sequence.
* @return the escaped character sequence
*/
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");
}
}
/**
* Remembers the current stream position.
* @return the current marked position
*/
protected String mark() throws ParseException {
if (debug) dbg_enter("mark");
try {
char next = lexer.lookAhead(0);
if (isMark(next)) {
lexer.consume(1);
return new StringBuffer().append(next).toString();
} else throw createParseException("mark");
} finally {
if (debug) dbg_leave("mark");
}
}
/**
* Gets the uric.
* @return the uric
*/
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 (ParseException ex) {
return null;
}
} finally {
if (debug) dbg_leave("uric");
}
}
/**
* Gets the uric without slashes.
* @return the uric string without slashes.
*/
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");
}
}
/**
* Gets the uric string.
* @return the uric string
*/
protected String uricString() {
StringBuffer retval = new StringBuffer();
while (true) {
String next = uric();
if (next == null) break;
retval.append(next);
}
return retval.toString();
}
/**
* Parses 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 ParsException if there was a problem parsing.
*/
public URI uriReference() throws ParseException {
if (debug) dbg_enter("uriReference");
URI retval = null;
Vector vect = lexer.peekNextToken(2);
Token t1 = (Token) vect.elementAt(0);
Token t2 = (Token) vect.elementAt(1);
try {
// System.out.println("token = " + t1.getTokenValue());
// System.out.println("tokentype = " + t1.getTokenType());
int type1, type2;
type1 = t1.getTokenType();
// Create an URI
if (t2.getTokenType() == ':') {
if (type1 == TokenTypes.SIP || type1 == TokenTypes.SIPS) {
// SIP or SIPS URL
retval = sipURL(t1);
} else if (type1 == TokenTypes.TEL) {
// TEL URL
retval = telURL();
} else {
// Generic URL
/*
* We can't throw an exception here because according to
* RFC 3261, p. 224 the scheme may be different from
* sip/sips/tel.
* In Connector.open(scheme) only 'sip' and 'sips'
* schemes are allowed, but this is not a problem
* because the package path where VM will try to find
* Protocol.class contains the scheme's name and an
* exception will be thrown if it is invalid.
*/
retval = new URI(lexer.getString('>'));
int pos = lexer.markInputPosition();
// It's safe to use 'pos-1' because at least
// one character ('>') was read from the buffer.
lexer.rewindInputPosition(pos - 1);
}
} else {
throw createParseException("Expecting \':\'");
}
} finally {
if (debug) dbg_leave("uriReference");
}
return retval;
}
/**
* Parses for the base phone number.
* @return the base phone number
* @exception ParseException if a parsingerror occurs
*/
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 (LexerCore.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");
}
}
/**
* Parses for the local phone number.
* @return the local phone number
* @exception ParseException if a parsing error occurs
*/
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 == ')' ||
LexerCore.isDigit(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");
}
}
/**
* Parses for telephone subscriber.
*
* @return the parsed telephone number.
* @exception ParseException if a parsing error occurs
*/
public final TelephoneNumber parseTelephoneNumber()
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();
else if (LexerCore.isAlpha(c) || LexerCore.isDigit(c) ||
c == '-' || c == '*' || c == '.' ||
c == '(' || c == ')' || c == '#') {
tn = local_phone_number();
} else throw createParseException("unexpected char " + c);
return tn;
} finally {
if (debug) dbg_leave("telephone_subscriber");
}
}
/**
* Gets the global phone number.
* @return the parsed phone number
* @exception ParseException ifa parsing error occurs
*/
private final TelephoneNumber global_phone_number()
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 == ';') {
nv = tel_parameters();
tn.setParameters(nv);
}
}
return tn;
} finally {
if (debug) dbg_leave("global_phone_number");
}
}
/**
* Gets the local phone number.
* @return the parsed phone number
* @exception ParseException ifa parsing error occurs
*/
private TelephoneNumber local_phone_number()
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: {
nv = tel_parameters();
tn.setParameters(nv);
break;
}
default: {
break;
}
}
}
} finally {
if (debug) dbg_leave("local_phone_number");
}
return tn;
}
/**
* Gets the telephone field parameters.
* @return the telephone parameters
* @exception ParseException if a parsing error occurs
*/
private NameValueList tel_parameters() throws ParseException {
NameValueList nvList = new NameValueList();
while (lexer.hasMoreChars()) {
lexer.consume(1);
NameValue nv = uriParam();
String nameParam = nv.getName();
String valueParam = (String)(nv.getValue());
if (nameParam.equalsIgnoreCase("isub")) {
// RFC 2806, 2.2
// isdn-subaddress = ";isub=" 1*phonedigit
// phonedigit = DIGIT / visual-separator
// visual-separator = "-" / "." / "(" / ")"
byte[] valueBytes = valueParam.getBytes();
for (int i = 0; i < valueBytes.length; i++) {
if (!isPhoneDigit(valueBytes[i])) {
throw new
IllegalArgumentException("Wrong isdn-subaddress");
}
}
} else if (nameParam.equalsIgnoreCase("postd")) {
// RFC 2806, 2.2
// post-dial = ";postd=" 1*(phonedigit /
// dtmf-digit / pause-character)
// dtmf-digit = "*" / "#" / "A" / "B" / "C" / "D"
// pause-character = one-second-pause / wait-for-dial-tone
// one-second-pause = "p"
// wait-for-dial-tone = "w"
byte[] valueBytes = valueParam.getBytes();
for (int i = 0; i < valueBytes.length; i++) {
int ch = valueBytes[i];
if (!isPhoneDigit(ch) && !isDtmf_digit(ch) &&
!isPauseChar(ch)) {
throw new
IllegalArgumentException("Wrong post-dial");
}
}
}
nvList.add(nv);
char tok = lexer.lookAhead(0);
if (tok == ';') continue;
else break;
}
return nvList;
}
/**
* Checks that the given symbol os a phonedigit.
* RFC 2806, 2.2
* phonedigit = DIGIT / visual-separator
* visual-separator = "-" / "." / "(" / ")"
* @param ch is a given byte to checking
* @return true if the given char is a phonedigit
*/
private boolean isPhoneDigit(int ch) {
return isCharFromString(ch, PHONE_DIGIT);
}
/**
* Checks that the given symbol is a dtmf digit.
* RFC 2806, 2.2
* dtmf-digit = "*" / "#" / "A" / "B" / "C" / "D"
* @param ch is a given byte to checking
* @return true if the given char is a dtmf digit
*/
private boolean isDtmf_digit(int ch) {
return isCharFromString(ch, DTMF_DIGIT);
}
/**
* Checks that the given symbol is a pause character.
* pause-character = one-second-pause / wait-for-dial-tone
* one-second-pause = "p"
* wait-for-dial-tone = "w"
* @param ch is a given byte to checking
* @return true if the given char is a dtmf digit
*/
private boolean isPauseChar(int ch) {
return isCharFromString(ch, PAUSE_CHAR);
}
/**
* Checks that the given symbol contains into a string.
* @param ch is a given byte to checking
* @param str is a string for checking
* @return true if the given char can be find in a string
*/
private boolean isCharFromString(int ch, String str) {
return (str.indexOf(ch) != -1);
}
/**
* Parses and returns a structure for a Tel URL.
* @return a parsed tel url structure.
* @exception ParseException if a parsing error occurs
*/
public TelURL telURL() throws ParseException {
lexer.match(TokenTypes.TEL);
lexer.match(':');
TelephoneNumber tn = this.parseTelephoneNumber();
TelURL telUrl = new TelURL();
telUrl.setTelephoneNumber(tn);
return telUrl;
}
/**
* Parses and returns a structure for a SIP URL.
* @param token the token of scheme (SIP or SIPS)
* @return a URL structure for a SIP url.
* @throws ParsException if there was a problem parsing.
* @throws IllegalArgumentException when parsing error is fatal.
*/
public SipURI sipURL(Token token) throws ParseException {
if (debug) dbg_enter("sipURL");
char la;
SipURI retval = new SipURI();
try {
lexer.match(token.getTokenType());
lexer.match(':');
retval.setScheme(token.getTokenValue());
if (!lexer.hasMoreChars()) { // sip: - server dedicated URI
retval.setServer();
return retval;
}
int m = lexer.markInputPosition();
// get user part
String user = user();
if (!lexer.hasMoreChars()) { // nosymbols after user name
lexer.rewindInputPosition(m); // move to start of host
} else { // check symbols after user name
// maybe sip:host
la = lexer.lookAhead(0);
// check that user field is not empty
if (la == ':' || la == '@') { // sip:user@... or sip:user:pass
if (user.length() == 0) {
throw new
IllegalArgumentException("User field is missed");
}
if (la == ':') {
// sip:user:passw...
lexer.consume(1);
String password = password();
if (!lexer.hasMoreChars()
|| lexer.lookAhead(0) != '@') {
if (StringTokenizer.isDigitString(password)) {
// maybe sip:host:port - move to start of host
lexer.rewindInputPosition(m);
} else { // sip:user:pass<wrong symbol>
throw new IllegalArgumentException
("Expecting \"@\"");
}
} else { // sip:user:pass@...
retval.setUser(user);
retval.setUserPassword(password);
lexer.consume(1);
}
} else { // la == '@'
retval.setUser(user);
lexer.consume(1);
}
} else { // no '@' after user field - maybe sip:host...
lexer.rewindInputPosition(m); // move to start of host
}
}
// check for sip:*
la = lexer.lookAhead(0);
if (la == '*') {
lexer.consume(1);
// server shared URI
retval.setServer();
retval.setShared();
} else if (la == ';') { // sip:;...
retval.setServer();
} else { // try to read host
// host parsing
HostNameParser hnp = new HostNameParser(this.getLexer());
HostPort hp = hnp.hostPort();
String host = hp.getHost().getHostname();
if (host == null) { // maybe sip:5060
if (hp.hasPort()) { // port only - server URI
retval.setServer();
} else { // sip:1234:5678 - wrong format
throw new
IllegalArgumentException("Illegal URI format");
}
}
retval.setHostPort(hp);
}
lexer.selectLexer("charLexer");
// parse parameters
while (lexer.hasMoreChars()) {
if (lexer.lookAhead(0) != ';') break;
lexer.consume(1);
NameValue parms = uriParam();
if (retval.hasParameter(parms.getName())) {
throw new IllegalArgumentException(
"Found duplicate of parameter ");
}
if (parms.isValueQuoted() && !retval.isServer()) {
throw new IllegalArgumentException(
"Client URI cannot contain quoted parameter ");
}
retval.setUriParameter(parms);
}
if (lexer.hasMoreChars() && lexer.lookAhead(0) == '?') {
if (retval.isServer()) {
throw new IllegalArgumentException(
"Server URI cannot contain headers");
}
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");
}
}
/**
* Peeks at the scheme field.
* @return the protocol scheme
* @exception ParseException if a parsing error occurs
*/
public String peekScheme() throws ParseException {
Vector tokens = lexer.peekNextToken(1);
if (tokens.size() == 0)
return null;
String scheme = ((Token)tokens.elementAt(0)).getTokenValue();
return scheme;
}
/**
* Gets a name value for a given query header (ie one that comes
* after the ?).
* @return name value pair for q-header
* @exception ParseException if a parsing error occurs
*/
protected NameValue qheader() throws ParseException {
String name = lexer.getNextToken('=');
lexer.consume(1);
String value = hvalue();
return new NameValue(name, value);
}
/**
* Gets a header value.
* @return value of current header
* @exception ParseException if a parsing error occurs
*/
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.
if (la == '+' || la == '?' || la == ':' || la == '@'
|| la == '[' || la == ']' || la == '/' || la == '$'
|| la == '_' || la == '-' || la == '"' || la == '!'
|| la == '~' || la == '*' || la == '.' || la == '('
|| la == ')' || LexerCore.isAlpha(la)
|| LexerCore.isDigit(la)) {
lexer.consume(1);
retval.append(la);
} else if (la == '%') {
retval.append(escaped());
} else break;
}
return retval.toString();
}
/**
* Scans 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).
* @exception ParseException if a parsing error occurs
*/
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();
}
/**
* Gets the user field from the URI.
* @return ths parsed user field
* @exception ParseException if a parsing error occurs
*/
protected String user() throws ParseException {
if (debug) dbg_enter("user");
try {
StringBuffer retval = new StringBuffer();
while (lexer.hasMoreChars()) {
char la = lexer.lookAhead(0);
if (isUnreserved(la) ||
isUserUnreserved(la)) {
retval.append(la);
lexer.consume(1);
} else if (isEscaped()) {
String esc = lexer.charAsString(3);
lexer.consume(3);
retval.append(esc);
} else break;
}
return retval.toString();
} finally {
if (debug) dbg_leave("user");
}
}
/**
* Gets the password field from the URI.
* @return ths parsed password field
* @exception ParseException if a parsing error occurs
*/
protected String password() throws ParseException {
StringBuffer retval = new StringBuffer();
while (true) {
char la = lexer.lookAhead(0);
if (isUnreserved(la) || la == '&' || la == '=' ||
la == '+' || la == '$' || la == ',') {
retval.append(la);
lexer.consume(1);
} else if (isEscaped()) {
String esc = lexer.charAsString(3);
retval.append(esc);
// CR FIX from Jeff Haynie frm JAIN-SIP
lexer.consume(3);
} else break;
}
return retval.toString();
}
/**
* Default parse method. This method just calls uriReference.
* @return ths parsed URI
* @exception ParseException if a parsing error occurs
*/
public URI parse() throws ParseException {
return uriReference();
}
/**
* Parse method with checking the rest of input URL.
* @return ths parsed URI
* @exception ParseException if a parsing error occurs
*/
public URI parseWholeString() throws ParseException {
URI retValue = uriReference();
if (lexer.hasMoreChars()) {
throw createParseException("redundant symbols");
}
return retValue;
}
}
|