/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (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/CDDLv1.0.html or
* glassfish/bootstrap/legal/CDDLv1.0.txt.
* See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at glassfish/bootstrap/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* you own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*/
package org.jvnet.glassfish.comms.sipagent.model;
import com.ericsson.ssa.sip.AddressHeaderValidator;
import com.ericsson.ssa.sip.CSeqHeaderValidator;
import com.ericsson.ssa.sip.CallIDValidator;
import com.ericsson.ssa.sip.MandatoryHeaderValidator;
import com.ericsson.ssa.sip.SipFactoryImpl;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.ListIterator;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.URI;
// inserted by hockey (automatic)
import java.util.logging.Logger;
import java.util.logging.Level;
import com.ericsson.ssa.container.SipParserErrorHandler;
import com.ericsson.ssa.sip.Header;
import com.ericsson.ssa.sip.MaxForwardsValidator;
import com.ericsson.ssa.sip.SipServletMessageImpl;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.ViaHeaderValidator;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;
import javax.servlet.sip.ServletParseException;
/**
* This is a very nice class that no-one had javadoc'ed before.
*
* ehswolm 2006-12-01: I don't feel like formatting this code just right now...
*
* @author ???
* @reviewed eandbur 2006-dec-01
*/
public class MySipParser {
private static Logger theirLog = (Logger) Logger.getLogger(
"org.jvnet.glassfish.comms.sipagent.model.MySipParser");
private static SipFactory sf = SipFactoryImpl.getInstance();
private static MySipParser _instance = null;
private ArrayList<MandatoryHeaderValidator> mandatoryHeaderValidators;
// Prevent instance creation of this singleton.
private MySipParser() {
// Initialization of validators
mandatoryHeaderValidators = new ArrayList<MandatoryHeaderValidator>();
//mandatoryHeaderValidators.add(new CallIDValidator());
//mandatoryHeaderValidators.add(new AddressHeaderValidator(Header.FROM));
//mandatoryHeaderValidators.add(new AddressHeaderValidator(Header.TO));
//mandatoryHeaderValidators.add(new CSeqHeaderValidator());
//mandatoryHeaderValidators.add(new ViaHeaderValidator());
//mandatoryHeaderValidators.add(new MaxForwardsValidator());
}
public synchronized static MySipParser getInstance() {
if (_instance != null) {
return _instance;
}
return _instance = new MySipParser();
}
public SipMessage parseMessage(
SipServletMessageImpl message,
ByteBuffer bb,
InetSocketAddress local,
TargetTuple remote,
SipParserErrorHandler errorHandler)
throws ParserException {
int limit = bb.limit();
long startTime = 0;
long endTime;
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Data is ready for process : " + limit);
}
if (theirLog.isLoggable(Level.FINE)) {
startTime = System.nanoTime();
}
bb.rewind();
byte[] bs = bb.array();
// Process the first line
int cnt = bb.position();
boolean isRequest = false;
boolean isResponse = false;
while (message == null && cnt < limit) {
try {
int position = bb.position();
int foundSipVersion = -1;
isRequest = false;
isResponse = false;
while (cnt + 1 < limit && (bs[cnt] != '\r' || bs[cnt + 1] != '\n')) {
// there is room for the version string
if (!isRequest && !isResponse && cnt + 6 < limit && (bs[cnt] == 's' || bs[cnt] == 'S')) { // Check
// for
// ip/2.0
if ((bs[cnt + 1] == 'i' || bs[cnt + 1] == 'I') && (bs[cnt + 2] == 'p' || bs[cnt + 2] == 'P') && bs[cnt + 3] == '/' && bs[cnt + 4] == '2' && bs[cnt + 5] == '.' && bs[cnt + 6] == '0') {
foundSipVersion = cnt; // Marks begining of SIP/2.0
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Found SIP/2.0 version at index = " + foundSipVersion);
}
// Check if it is at the end, then it is a request
if (cnt + 8 < limit && bs[cnt + 7] == '\r' && bs[cnt + 8] == '\n') {
isRequest = true;
cnt += 6;
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "The message is a SIP request");
}
} else if (cnt + 7 < limit && bs[cnt + 7] == ' ') {
isResponse = true;
cnt += 7;
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "The message is a SIP response");
}
} else // Not a valid SIP/2.0 string
{
cnt += 6;
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Not a valid SIP/2.0 string");
}
foundSipVersion = -1;
}
}
}
cnt++;
}
if (cnt + 2 < limit) {
bb.position(cnt + 2);
} else {
theirLog.log(Level.WARNING, "returning null!");
return null;
}
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "The first line is : " + new String(bs, position, (cnt - position), SipFactoryImpl.SIP_CHARSET));
}
if (isRequest) {
int x = foundSipVersion - 2;
while (x > 0 && bs[x] != ' ') {
x--;
}
String reqUri = new String(bs, x + 1, (foundSipVersion - x - 2), SipFactoryImpl.SIP_CHARSET);
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "ReqUri = " + reqUri + " x = " + x + " len = " + (foundSipVersion - x - 2));
}
if (x == 0) {
// TODO point after CRLF
// bb.position()
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Error in parser the first line is corrupt!");
}
return null;
}
// The rest is method
// TODO check for capital case e.g. INVITE
int foundMethod = x--;
while (x > 0 && bs[x] > 0x40 && bs[x] < 0x5B) {
x--;
}
String method = new String(bs, 0, (foundMethod - 0), SipFactoryImpl.SIP_CHARSET);
String nice = new String(bs, x + 1, (foundMethod - x + 1), SipFactoryImpl.SIP_CHARSET);
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "method = " + method + " nice = " + nice);
}
URI uri = sf.createURI(reqUri);
if (uri == null) {
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Error could not create Request Uri.");
}
return null;
}
message = new SipServletRequestImpl(method, uri, SipFactoryImpl.PROTOCOL_LINE);
message.setLocal(local);
message.setRemote(remote);
} else if (isResponse) { // Start after the SIP/2.0 token
int x = foundSipVersion + 8; // SIP/2.0 len +1
int start = foundSipVersion + 8;
while (x < cnt && bs[x] != ' ') {
x++;
}
String codeStr = new String(bs, start, (x - start), SipFactoryImpl.SIP_CHARSET);
String phrase = new String(bs, (x + 1), (cnt - x - 1), SipFactoryImpl.SIP_CHARSET);
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "codeStr = " + codeStr + " phrase = " + phrase);
}
int code = Integer.parseInt(codeStr);
if (699 < code || code < 100) {
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Error code out of range in response : " + code);
}
return null;
}
message = new SipServletResponseImpl(null, SipFactoryImpl.PROTOCOL_LINE, code, phrase);
message.setLocal(local);
message.setRemote(remote);
} else {
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "The URI is not a request nor a response!");
}
return null;
}
} catch (ServletParseException spe) {
if (theirLog.isLoggable(Level.FINER)) {
theirLog.log(Level.FINER, "Error in parser", spe);
}
return null;
} catch (UnsupportedEncodingException uee) {
if (theirLog.isLoggable(Level.FINER)) {
theirLog.log(Level.FINER, "Error in parser", uee);
}
return null;
} catch (RuntimeException re) {
if (theirLog.isLoggable(Level.FINER)) {
theirLog.log(Level.FINER, "Error in parser", re);
}
return null;
}
cnt += 2; // Continuing point to the firs character after the initial
// line CRLF
}
//cnt += 2; // Continuing point to the firs character after the initial
// line CRLF
if (message == null) {
return null;
}
if (cnt < limit && !message.isHeadersComplete()) {
Header header = null;
int quoteCnt = 0;
boolean startToken = true;
int position = bb.position();
boolean incrCnt; // Whether the cnt should be incremented when continuing for loop
for (; cnt < limit;) {
incrCnt = true; // Default is increment, reset every time.
if (bs[cnt] == ':' && header == null) {
if (cnt - position < 1) {
// Line starts with colon. Do not accept
theirLog.severe("Line starts with colon, unacceptable");
if (errorHandler != null) {
errorHandler.handleError(((SipServletRequestImpl) (message)), 400, "Malformed header");
}
return null;
}
header = Header.create(bs, position, (cnt - position), message);
position = cnt + 1;
startToken = true;
} else if (startToken && (bs[cnt] == ' ' || bs[cnt] == '\t')) {
position = cnt + 1;
} else if (bs[cnt] == '"') {
quoteCnt++;
} else if (bs[cnt] == ',' && quoteCnt % 2 == 0 && header != null) { // multi header on one line
// header.setInLineValues(true);
// Must check if Date header
if (!Header.isSingleLineHeader(header) && !((cnt - position) == 3 && cnt + 1 < limit && bs[cnt + 1] == ' ')) {
// TODO - Should we throw a parse exception if in single
// line
// header map?
// This will have an impact on performance so it has to
// be
// measured.
// Eg trap Unsupported op ex and generate parse ex.
try {
header.setValue(
new String(bs, position, (cnt - position), SipFactoryImpl.SIP_CHARSET),
false);
} catch (UnsupportedEncodingException e) {
throw new ParserException("Failed setting header value", e);
}
header.setIsNextValueOnSameLine(true, false);
position = ++cnt;
// Trim WS
while (cnt < limit && bs[cnt] == ' ') {
position = ++cnt;
}
if (Header.isSingleLineHeader(header) && theirLog.isLoggable(Level.FINER)) {
theirLog.log(Level.FINER, "SingleLineHeader " + header.getName() + " is parsed as multiline header with a separator \',\' ");
if (theirLog.isLoggable(Level.FINEST)) {
theirLog.log(Level.FINEST, "The SingleLineHeader value : " + header.getValue() + " will be overwritten!");
}
}
}
} else if ((cnt + 1) < limit && bs[cnt] == '\r' && bs[cnt + 1] == '\n') {
if (header == null) {
message.setHeadersComplete(true);
cnt += 2;
// MARK
bb.position((cnt > limit) ? limit : cnt);
break;
} else if (cnt + 3 < limit && bs[cnt + 2] == '\r' && bs[cnt + 3] == '\n') {
// The rest is body
try {
header.setValue(
new String(bs, position, (cnt - position), SipFactoryImpl.SIP_CHARSET),
false);
} catch (UnsupportedEncodingException e) {
throw new ParserException("Failed setting header value", e);
}
header.setIsNextValueOnSameLine(false, false);
message.addHeader(header);
message.setHeadersComplete(true);
cnt += 4;
// MARK
bb.position((cnt > limit) ? limit : cnt);
if (theirLog.isLoggable(Level.FINE)) {
if (Header.isSingleLineHeader(header)) {
theirLog.log(Level.FINE, "Adding Header " + header.getName() + " = " + header.getValue() + " (" + bb.position() + ')');
} else {
ListIterator<String> li = header.getValues();
int x = 0;
while (li.hasNext()) {
theirLog.log(Level.FINE, "Adding Header " + header.getName() + " = " + li.next() + " (" + bb.position() + ')' + '<' + (++x) + '>');
}
}
}
break;
} else if (cnt + 2 < limit && (bs[cnt + 2] == ' ' || bs[cnt + 2] == '\t')) {
// Append String - multi line header
bs[cnt] = ' ';
bs[cnt + 1] = ' ';
bs[cnt + 2] = ' ';
cnt += 3;
} else {
try {
header.setValue(
new String(bs, position, (cnt - position), SipFactoryImpl.SIP_CHARSET),
false);
} catch (UnsupportedEncodingException e) {
throw new ParserException("Failed setting header value", e);
}
header.setIsNextValueOnSameLine(false, false);
message.addHeader(header);
cnt += 2;
// MARK
bb.position((cnt > limit) ? limit : cnt);
if (theirLog.isLoggable(Level.FINE)) {
if (Header.isSingleLineHeader(header)) {
theirLog.log(Level.FINE, "Adding Header " + header.getName() + " = " + header.getValue() + " (" + bb.position() + ')');
} else {
ListIterator<String> li = header.getValues();
int x = 0;
while (li.hasNext()) {
theirLog.log(Level.FINE, "Adding Header " + header.getName() + " = " + li.next() + " (" + bb.position() + ')' + '<' + (++x) + '>');
}
}
}
header = null;
quoteCnt = 0;
startToken = true;
// Don't increment as we have already stepped forward.
incrCnt = false;
}
position = cnt;
} else if (startToken) {
startToken = false;
}
// Do the increment if we should.
if (incrCnt) {
cnt++;
}
}
}
if (message.isHeadersComplete() && !message.isMessageComplete()) {
String errorStr = null;
Header header = message.getRawHeader(Header.CALL_ID);
if (header == null || header.getValue() == null || header.getValue().length() == 0) {
errorStr = "Failed to validate mandatory header CallId";
}
header = message.getRawHeader(Header.TO);
if (header == null || header.getValue() == null || header.getValue().length() == 0) {
errorStr = "Failed to validate mandatory header To";
}
header = message.getRawHeader(Header.FROM);
if (header == null || header.getValue() == null || header.getValue().length() == 0) {
errorStr = "Failed to validate mandatory header From";
}
header = message.getRawHeader(Header.CSEQ);
if (header == null || header.getValue() == null || header.getValue().length() == 0) {
errorStr = "Failed to validate mandatory header Cseq";
}
header = message.getRawHeader(Header.VIA);
if (header == null || header.getValue() == null || header.getValue().length() == 0) {
errorStr = "Failed to validate mandatory header Via";
}
header = message.getRawHeader(Header.MAX_FORWARDS);
if ((header == null || header.getValue() == null) && message instanceof SipServletRequestImpl) {
Header mf = Header.createFormated(Header.MAX_FORWARDS, message);
mf.setValue("70", false);
message.setHeader(mf);
if (theirLog.isLoggable(Level.FINER)) {
theirLog.log(Level.FINER, "Added Max-Forwards = " + 70);
}
}
for (MandatoryHeaderValidator validator : mandatoryHeaderValidators) {
if (!validator.validate(message)) {
errorStr = "Failed to parse mandatory header \'" + validator.getHeaderName() + "\'";
if (errorHandler != null) {
errorHandler.handleError(((SipServletRequestImpl) (message)), 400, errorStr);
}
theirLog.severe("failed to parse mandatory header " + validator.getHeaderName());
return null;
}
}
// validate mandatory header Max-Forwards";
// TODO - Create max fw = 70 maybe not here
if (errorStr != null) {
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, errorStr + " \r\n" + message);
}
return null;
}
if (theirLog.isLoggable(Level.FINE)) {
endTime = System.nanoTime();
theirLog.log(Level.FINE, "Headers = " + (endTime - startTime) / 1000 + "microsecs");
}
int contentLength = message.getContentLengthHeader();
if (contentLength < 0) { // No Content-Length
if (message.getRemote().getProtocol() == SipTransports.UDP_PROT) {
contentLength = bb.remaining();
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Assigning missing Content-Length");
}
// decision by stoffe to insert Content-Length header if not
// previously present
message.internalSetContentLength(contentLength);
} else {
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Content length is mandatory for TCP!");
}
return null;
}
}
boolean contentComplete = false;
if (contentLength == 0) {
contentComplete = true;
}
if (contentComplete == false) {
int position = bb.position();
int remaining = bb.remaining();
int bytesToCopy;
int remainingContent = (contentLength - message.getContentOffset());
if (remainingContent < remaining) {
bytesToCopy = remainingContent;
} else {
bytesToCopy = remaining;
}
if (bytesToCopy > 0) {
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Copy content bytes:" + bytesToCopy);
}
message.addContent(bs, position, bytesToCopy);
remainingContent = (contentLength - message.getContentOffset());
if (remainingContent == 0) {
contentComplete = true;
}
if (theirLog.isLoggable(Level.FINE)) {
if (remainingContent != 0) {
theirLog.log(Level.FINE, "Content remaining after this chunk:" + remainingContent);
}
}
bb.position(position + bytesToCopy);
}
}
if (contentComplete) {
if (theirLog.isLoggable(Level.FINE)) {
theirLog.log(Level.FINE, "Message content is complete. --> Message is complete took : ");
}
message.setMessageComplete(true);
if (theirLog.isLoggable(Level.FINE)) {
endTime = System.nanoTime();
theirLog.log(Level.FINE, "Total = " + (endTime - startTime) / 1000 + "microsecs");
}
}
}
SipMessage result = isRequest ? new SipRequest() : (isResponse ? new SipResponse() : null);
result.setImpl(message);
return result;
}
}