/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipURI;
/**
* @author ekrigro
* @reviewed qmigkra 2006-nov-17
*/
public class SipURIImpl extends URIImpl implements SipURI, Externalizable {
/**
*
*/
private static final long serialVersionUID = 3616450094292218936L;
static String DUPLICATE_PARAM = "The parsed SIP URI has two parameter with the same name : ";
public static final String LOOSE_ROUTE_PARAM = "lr";
public static final String MADDR_PARAM = "maddr";
public static final String METHOD_PARAM = "method";
public static final String TTL_PARAM = "ttl";
public static final String TRANSPORT_PARAM = "transport";
public static final String USER_PARAM = "user";
public static final String FRAGID_PARAM = "fid";
private static SipURIDecoder _uriDecoder = new SipURIDecoder();
private static SipURIEncoder _uriEncoder = new SipURIEncoder();
private boolean _secure = false;
private Ascii7String _user = null;
private Ascii7String _password = null;
private Ascii7String _host = null;
private int _port = -1;
/**
* Special handling of SipURI for Contact Header.
* If an Application retrieves a Contact header by calling getAddressHeader("Contact").getUri(),
* the flag isModifingContactUri is set to true. In this mode the SipUri imposes restriction to
* modify parts of the URI so that.
*
* 1. Set the user part of the uri.
* 2. Set parameters, except for "method", "ttl", "maddr" and "lr"
* 3. 200 OK as response to an OPTIONS allows the application to set alternative names and
* methods to reach the user.
*
*/
//private boolean isModifingContactUri = false;
// private ParameterByteMap _parameters = null;
private ParameterByteMap _headers = null;
public SipURIImpl() {
} // used only in parser and serialization
public SipURIImpl(boolean secure, String user, String host) {
_secure = secure;
_user = new Ascii7String(user);
_host = new Ascii7String(host);
}
public SipURIImpl(String protocol, String uri, int offset)
throws ServletParseException {
if (protocol.equals(SipFactoryImpl.SIPS_URI_PROTOCOL)) {
_secure = true;
}
try {
parse(uri, offset, uri.length());
} catch (RuntimeException e) {
throw new ServletParseException(
"Unexpected exception while parsing URI: " + e);
}
}
// For RMI serialization
public void writeExternal(ObjectOutput output) throws IOException {
try {
// _secure, _user, _password, _host, _port, _parameters, _headers
output.writeBoolean(_secure);
if (_user != null) { // Special treatment since it can be null and writeUTF can't be used
output.writeInt(_user.length());
output.write(_user.getBytes());
} else {
output.writeInt(0);
}
if (_password != null) {
output.writeInt(_password.length());
output.write(_password.getBytes());
} else {
output.writeInt(0);
}
output.writeUTF(_host.toString());
output.writeInt(_port);
if (_parameters != null && _parameters.toArray() != null) {
byte[] parameter_data = _parameters.toArray();
output.writeInt(parameter_data.length); // write #bytes
output.write(parameter_data);
} else {
output.writeInt(0);
}
if (_headers != null && _headers.toArray() != null) {
byte[] headers_data = _headers.toArray();
output.writeInt(headers_data.length); // write #bytes
output.write(headers_data);
} else {
output.writeInt(0);
}
} catch (Exception ignore) {
}
}
public void readExternal(ObjectInput input) throws IOException {
try {
_secure = input.readBoolean();
int len = input.readInt();
if (len > 0) {
byte[] b = new byte[len];
input.read(b);
_user = new Ascii7String(b);
}
len = input.readInt();
if (len > 0) {
byte[] b = new byte[len];
input.read(b);
_password = new Ascii7String(b);
}
_host = new Ascii7String(input.readUTF());
_port = input.readInt();
len = input.readInt(); // read parameter #bytes
if (len > 0) {
byte[] parameter_data = new byte[len];
int readBytes = input.read(parameter_data, 0, len);
if (readBytes != len) {
throw new IOException("failed to read parameter section len:" +
len);
}
_parameters = new ParameterByteMap(parameter_data, ';');
}
len = input.readInt(); // read headers #bytes
if (len > 0) {
byte[] headers_data = new byte[len];
int readBytes = input.read(headers_data, 0, len);
if (readBytes != len) {
throw new IOException("failed to read headers section len:" +
len);
}
_headers = new ParameterByteMap(headers_data, ';');
}
} catch (Exception ignore) {
}
}
private void parse(String str, int offset, int length)
throws ServletParseException {
int passwdIndex = -1; // colon
int portIndex = -1; // colon
int semiIndex = -1;
int qIndex = -1;
int atIndex = -1;
int endHostIndex = length;
int endParamIndex = length;
for (int i = offset; i < length; i++) {
// Control character should not appear in the url.
if (Character.isISOControl(str.charAt(i))) {
throw new ServletParseException(
"The URI contains a control character.");
}
if (str.charAt(i) == ':' && semiIndex == -1 ) {
if (atIndex > 0) {
portIndex = i;
} else {
passwdIndex = i;
}
} else if ((atIndex == -1) && (str.charAt(i) == '@')) {
atIndex = i;
} else if ((semiIndex == -1) && (str.charAt(i) == ';')) {
semiIndex = i;
endHostIndex = i;
} else if ((semiIndex != -1) && (str.charAt(i) == ';') && semiIndex < atIndex ) {
semiIndex = i;
endHostIndex = i;
} else if (str.charAt(i) == '?') {
qIndex = i;
if (semiIndex > 0) {
endParamIndex = i;
} else {
endHostIndex = i;
}
break;
}
}
if ((passwdIndex > 0) && (atIndex < 0)) {
portIndex = passwdIndex;
}
/*
* INFO: The user part and parameter parts of the url, could contains
* escaped characters, so we need to unecape them.
*/
// Check user
try {
if (atIndex > 0) {
if (passwdIndex > 0) {
_user = new Ascii7String(str.substring(offset,
passwdIndex));
_password = new Ascii7String(str.substring(passwdIndex + 1,
atIndex));
} else {
_user = new Ascii7String(str.substring(offset, atIndex));
}
offset = atIndex + 1;
}
// Ex: "sip:+302106699011;tgrp=0;trunk-context=0@164.48.135.94:5060"
if ( endHostIndex < offset ) {
endHostIndex = str.length();
String host = str.substring(offset, endHostIndex);
int innerCheck =host.indexOf(":");
if ( innerCheck != -1 )
portIndex = offset + innerCheck;
}
// Now the hostpart that is mandatory
if (portIndex > 0) { // Host n port
_host = new Ascii7String(str.substring(offset, portIndex));
_port = Integer.parseInt(str.substring(portIndex + 1,
endHostIndex));
} else { // no port only host
_host = new Ascii7String(str.substring(offset,
endHostIndex));
}
} catch (IllegalStateException e) {
throw new ServletParseException("CharacterCodingException");
}
offset = endHostIndex + 1;
// Time for the parameters
if (semiIndex > 0) {
int numBytes = endParamIndex - semiIndex;
_parameters = new ParameterByteMap(str.getBytes(), semiIndex,
numBytes,
';');
}
// And last and least the headers
if (qIndex > 0) {
int endIndex = length - 1;
int numBytes = endIndex - qIndex;
_headers =
new ParameterByteMap(str.getBytes(), qIndex, numBytes + 1,
'&');
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getUser()
*/
public String getUser() {
try {
if (_user == null) {
return null;
}
return _uriDecoder.decode(_user.toString());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setUser(java.lang.String)
*/
public void setUser(String user) {
verify();
if (user != null && user.trim().length()>0) {
_user = new Ascii7String(_uriEncoder.encodeUserPart(user));
} else {
_user = null;
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getUserPassword()
*/
public String getUserPassword() {
try {
if (_password == null) {
return null;
}
return _uriDecoder.decode(_password.toString());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setUserPassword(java.lang.String)
*/
public void setUserPassword(String passwd) {
verify();
if (passwd != null && passwd.trim().length()>0) {
_password = new Ascii7String(_uriEncoder.encodePasswordPart(passwd));
}
else {
_password = null;
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getHost()
*/
public String getHost() {
return _host.toString();
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setHost(java.lang.String)
*/
public void setHost(String host) {
verify();
__setHost(host);
}
void __setHost(String host) {
_host = new Ascii7String(host);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getPort()
*/
public int getPort() {
return _port;
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setPort(int)
*/
public void setPort(int port) {
verify();
__setPort(port);
}
void __setPort(int port) {
if (port < 0) {
_port = -1;
} else {
_port = port;
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#isSecure()
*/
public boolean isSecure() {
return _secure;
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setSecure(boolean)
*/
public void setSecure(boolean sec) {
verify();
_secure = sec;
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getTransportParam()
*/
public String getTransportParam() {
return getParameter(TRANSPORT_PARAM);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setTransportParam(java.lang.String)
*/
public void setTransportParam(String value) {
setParameter(TRANSPORT_PARAM, value);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getMAddrParam()
*/
public String getMAddrParam() {
return getParameter(MADDR_PARAM);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setMAddrParam(java.lang.String)
*/
public void setMAddrParam(String value) {
setParameter(MADDR_PARAM, value);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getMethodParam()
*/
public String getMethodParam() {
return getParameter(METHOD_PARAM);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setMethodParam(java.lang.String)
*/
public void setMethodParam(String value) {
setParameter(METHOD_PARAM, value);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getTTLParam()
*/
public int getTTLParam() {
int ttl = -1;
String value = getParameter(TTL_PARAM);
if (value != null) {
try {
ttl = Integer.parseInt(value);
} catch (Exception e) {
} // Bad luck :-)
}
return ttl;
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setTTLParam(int)
*/
public void setTTLParam(int value) {
setParameter(TTL_PARAM, String.valueOf(value));
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getUserParam()
*/
public String getUserParam() {
return getParameter(USER_PARAM);
}
@Override
public void setParameter(String name, String value) {
super.setParameter(name, value);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setUserParam(java.lang.String)
*/
public void setUserParam(String value) {
setParameter(USER_PARAM, value);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setLrParam(boolean)
*/
public void setLrParam(boolean lr) {
if (lr) {
setParameter(LOOSE_ROUTE_PARAM, "");
} else {
removeParameter(LOOSE_ROUTE_PARAM);
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getHeader(java.lang.String)
*/
public String getHeader(String header) {
if (_headers == null) {
return null;
}
return _headers.get(header);
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#setHeader(java.lang.String,
* java.lang.String)
*/
public void setHeader(String header, String value) {
verify();
if (_headers == null) {
_headers = new ParameterByteMap('&');
}
_headers.put(header, value);
}
public void removeHeader(String name) {
verify();
if (_headers != null) {
_headers.remove(name);
}
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.SipURI#getHeaderNames()
*/
public Iterator<String> getHeaderNames() {
if (_headers == null) {
return Collections.EMPTY_LIST.iterator();
}
return _headers.getKeys();
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.URI#getScheme()
*/
public String getScheme() {
return _secure ? SipFactoryImpl.SIPS_URI_PROTOCOL
: SipFactoryImpl.SIP_URI_PROTOCOL;
}
/*
* (non-Javadoc)
*
* @see javax.servlet.sip.URI#isSipURI()
*/
public boolean isSipURI() {
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#clone()
*/
public SipURIImpl clone() {
SipURIImpl newUri = new SipURIImpl();
newUri._secure = _secure;
// avoid String(String) due to memory overhead when a explicit copy is
// created
// copies is Unnecesary because String are immutable!!
// if (_user != null) newUri._user = new String(_user);
// if (_password != null) newUri._password = new String(_password);
// if (_host != null) newUri._host = new String(_host);
newUri._user = _user;
newUri._password = _password;
newUri._host = _host;
newUri._port = _port;
if (_parameters != null) {
newUri._parameters = (ParameterByteMap) _parameters.clone();
// remove tag Due to strange requirement on Address to not clone tag
newUri._parameters.remove(AddressImpl.TAG_PARAM);
}
if (_headers != null) {
newUri._headers = (ParameterByteMap) _headers.clone();
}
return newUri;
}
/*
* RFC3261 19.1.4
*
*
*
* - Comparison of the userinfo of SIP and SIPS URIs is case-sensitive
*
* - The ordering of parameters and header fields is not significant
* in comparing SIP and SIPS URIs.
*
* - Characters other than those in the "reserved" set (RFC 2396)
* are equivalent to their ""%" HEX HEX" encoding.
*
* - An IP address that is the result of a DNS lookup of a host name
* does not match that host name
*
* -
*
*
*
*
*
*
*
*
*
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if (!(o instanceof SipURI)) {
return false;
}
SipURI uri = (SipURI) o;
if (_port != uri.getPort()) {
return false;
}
if (_secure != uri.isSecure()) {
return false;
}
if (((_user == null) && (uri.getUser() != null)) ||
((_user != null) && (uri.getUser() == null))) {
return false;
}
if ((_user != null) && !getUser().equals(uri.getUser())) {
return false;
}
if (((_password == null) && (uri.getUserPassword() != null)) ||
((_password != null) && (uri.getUserPassword() == null))) {
return false;
}
if ((_password != null) &&
!getUserPassword().equals(uri.getUserPassword())) {
return false;
}
if ( ( (getHost() == null) && (uri.getHost() != null) ) ||
( (uri.getHost() == null) && (this.getHost() == null)) ) {
return false;
}
if (getHost() != null && uri.getHost() != null && !getHost().equalsIgnoreCase(uri.getHost()) ) {
return false;
}
// Compare headers
HashMap<String,String> thisHeaders = new HashMap<String,String>();
Iterator<String> thisHeaderIterator = getHeaderNames();
while (thisHeaderIterator.hasNext()) {
String key = (String) thisHeaderIterator.next();
thisHeaders.put(key,this.getHeader(key));
}
HashMap<String,String> uriHeaders = new HashMap<String,String>();
Iterator<String> uriHeaderIterator = uri.getHeaderNames();
while (uriHeaderIterator.hasNext()) {
String key = (String) uriHeaderIterator.next();
uriHeaders.put(key, uri.getHeader(key));
}
if ( !thisHeaders.equals(uriHeaders) ) {
return false;
}
// Compare parameters
Map<String,String> thisParameters = new HashMap<String,String>(8);
Iterator<String> thisParameterIterator = getParameterNames();
while (thisParameterIterator.hasNext()) {
String key = (String) thisParameterIterator.next();
//adding keys lowercase and values as normal
thisParameters.put(key.toLowerCase(), this.getParameter(key));
}
Map<String,String> uriParameters = new HashMap<String,String>(8);
Iterator<String> uriParameterIterator = uri.getParameterNames();
while (uriParameterIterator.hasNext()) {
String key = (String) uriParameterIterator.next();
//adding keys lowercase and values as normal
uriParameters.put(key.toLowerCase(), uri.getParameter(key));
}
if (compareParameters(thisParameters, uriParameters)) {
//swap and compare from the other collection point of view, since after iteration the second collection may contains special parameters, such as TTL
return compareParameters(uriParameters, thisParameters);
}
return false;
}
/**
* Comparing parameters in the URI According to 19.1.4 URI Comparison comparing parameters
* @param sourceParams
* @param destParams
* @return
*/
private boolean compareParameters(Map<String, String> sourceParams, Map<String, String> destParams) {
//� Any uri-parameter appearing in both URIs must match. If they are not existing on both, then ignore comparing them
Iterator<String> keysIterator = sourceParams.keySet().iterator();
while (keysIterator.hasNext()) {
String srcKey = keysIterator.next();
//special handling for MADDR_PARAM, TTL_PARAM, they must exists with same values
if (srcKey.equals(MADDR_PARAM) || srcKey.equals(TTL_PARAM) || srcKey.equals(TRANSPORT_PARAM)
|| srcKey.equals(USER_PARAM) || srcKey.equals(METHOD_PARAM)) {
if (!destParams.containsKey(srcKey) || !sourceParams.get(srcKey).toLowerCase().equals(destParams.get(srcKey).toLowerCase())) {
return false;
}
else {
//remove it so we can compare the dest to src and not comparing these two next time
keysIterator.remove();
destParams.remove(srcKey);
}
} else {
//if there is a key, lets compare the values non case sensitive
if (destParams.containsKey(srcKey)) {
if (!sourceParams.get(srcKey).toLowerCase().equals(destParams.get(srcKey).toLowerCase())) {
return false;
}
else {
//remove it so we can compare the dest to src and not comparing these two next time
keysIterator.remove();
destParams.remove(srcKey);
}
}
}
}
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
// Not all needs to be unique
// A ^ (((B >> 16) & 0x0000FFFF) | ((B<<16) & 0xFFFF0000))
// Or try just ^ if this is to slow
int result = _host.hashCode() ^ _port;
if (_user != null) {
result = _user.hashCode() ^ result;
}
return result;
}
/*
* Build up the sip uri. If the user or the parameter part contains any
* unescaped characters then escape them.
*
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuilder sb = new StringBuilder(getScheme());
sb.append(':');
if (_user != null) {
sb.append(_user.toString());
if (_password != null) {
sb.append(':');
sb.append(_password.toString());
}
sb.append('@');
}
sb.append(_host.toString());
if (_port > 0) {
sb.append(':');
sb.append(_port);
}
// Duplicated in AddressImpl
if (_parameters != null) {
sb.append(_parameters.toString());
}
if (_headers != null) {
sb.append(_headers.toString());
}
return sb.toString();
}
void removeToTag() {
if (_parameters != null) {
_parameters.remove(AddressImpl.TAG_PARAM);
}
}
}