/*
* 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 java.util.Vector;
import java.io.*;
import javax.microedition.sip.SipException;
import gov.nist.siplite.*;
import gov.nist.siplite.header.*;
import gov.nist.siplite.message.*;
import gov.nist.siplite.address.*;
import gov.nist.core.*;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;
/**
* Parse SIP message and parts of SIP messages such as URI's etc
* from memory and return a structure.
* Intended use: UDP message processing.
* This class is used when you have an entire SIP message or Header
* or SIP URL in memory and you want to generate a parsed structure from
* it. For SIP messages, the payload can be binary or String.
* If you have a binary payload,
* use parseMessage(byte[]) else use parseSIPMessage(String)
* The payload is accessible from the parsed message using the getContent and
* getContentBytes methods provided by the Message class.
* Currently only eager parsing of the message is supported (i.e. the
* entire message is parsed in one feld swoop).
*
*
* @version JAIN-SIP-1.1
*
*
* <a href="{@docRoot}/uncopyright.html">This code is in the public domain.</a>
*
*/
public class StringMsgParser {
/** Flag indicating body read requested. */
protected boolean readBody;
/** Unprocessed message part 1 (for error reporting). */
private String rawMessage;
/** Unprocessed message part 2 (for error reporting). */
private String rawMessage1;
/** Current message. */
private String currentMessage;
/** Parsing exeception listener. */
private ParseExceptionListener parseExceptionListener;
/** Message headers. */
private Vector messageHeaders;
/** Current buffer pointer. */
private int bufferPointer;
/** Flag indicating bodyis a text string. */
private boolean bodyIsString;
/** Current message contents as an arrayof bytes. */
private byte[] currentMessageBytes;
/** Lengthg of current message body. */
protected int contentLength;
/** Debugging enabled flag. */
private boolean debugFlag;
/** Current line being parsed. */
private int currentLine;
/** Current header being processed. */
private String currentHeader;
/** Default constructor. */
public StringMsgParser() {
super();
messageHeaders = new Vector(10, 10);
bufferPointer = 0;
currentLine = 0;
readBody = true;
}
/**
* Constructor (given a parse exception handler).
* @since 1.0
* @param exhandler is the parse exception listener for the message parser.
*/
public StringMsgParser(ParseExceptionListener exhandler) {
this();
parseExceptionListener = exhandler;
}
/**
* Gets the message body.
* @return the message body
*/
protected String getMessageBody() {
if (this.contentLength == 0) {
return null;
} else {
int endIndex = bufferPointer + this.contentLength;
String body;
// guard against bad specifications.
if (endIndex > currentMessage.length()) {
endIndex = currentMessage.length();
body = currentMessage.substring(bufferPointer, endIndex);
bufferPointer = endIndex;
} else {
body = currentMessage.substring(bufferPointer, endIndex);
bufferPointer = endIndex + 1;
}
this.contentLength = 0;
return body;
}
}
/**
* Gets the message body as a byte array.
* @return the mesage body
*/
protected byte[] getBodyAsBytes() {
if (this.contentLength == 0) {
return null;
} else {
int endIndex = bufferPointer + this.contentLength;
// guard against bad specifications.
if (endIndex > currentMessageBytes.length) {
endIndex = currentMessageBytes.length;
}
byte[] body = new byte[endIndex - bufferPointer];
System.arraycopy
(currentMessageBytes, bufferPointer, body, 0, body.length);
bufferPointer = endIndex;
this.contentLength = 0;
return body;
}
}
/**
* Returns the contents till the end of the buffer (this is useful when
* you encounter an error.
* @return text up to end of message
*/
protected String readToEnd() {
String body = currentMessage.substring(bufferPointer);
bufferPointer += body.length();
return body;
}
/**
* Returns the bytes to the end of the message.
* This is invoked when the parser is invoked with an array of bytes
* rather than with a string.
* @return the bytes to the end of message
*/
protected byte[] readBytesToEnd() {
byte[] body = new byte[currentMessageBytes.length - bufferPointer];
int endIndex = currentMessageBytes.length;
for (int i = bufferPointer, k = 0; i < endIndex; i++, k++) {
body[k] = currentMessageBytes[i];
}
bufferPointer = endIndex;
this.contentLength = 0;
return body;
}
/**
* Adds a handler for header parsing errors.
* @param pexhandler is a class
* that implements the ParseExceptionListener interface.
*/
public void setParseExceptionListener
(ParseExceptionListener pexhandler) {
parseExceptionListener = pexhandler;
}
/**
* Returns true if the body is encoded as a string.
* If the parseMessage(String) method is invoked then the body
* is assumed to be a string.
* @return true if body is a string
*/
protected boolean isBodyString() {
return bodyIsString;
}
/**
* Parses a buffer containing a single SIP Message where the body
* is an array of un-interpreted bytes. This is intended for parsing
* the message from a memory buffer when the buffer.
* Incorporates a bug fix for a bug that was noted by Will Sullin of
* Callcast
* @param msgBuffer a byte buffer containing the messages to be parsed.
* This can consist of multiple SIP Messages concatenated together.
* @return a Message[] structure (request or response)
* containing the parsed SIP message.
* @exception ParseException is thrown when an
* illegal message has been encountered (and
* the rest of the buffer is discarded).
* @see ParseExceptionListener
*/
public Message parseSIPMessage(byte[] msgBuffer)
throws ParseException {
bufferPointer = 0;
bodyIsString = false;
currentMessageBytes = msgBuffer;
int start;
// Squeeze out leading CRLF
// Squeeze out the leading nulls (otherwise the parser will crash)
for (start = bufferPointer; start < msgBuffer.length; start++) {
final char chr = (char)msgBuffer[start];
if (chr != '\r'
&& chr != '\n'
&& chr != '\0') break;
}
if (start == msgBuffer.length)
return null;
// Find the end of the SIP message.
int fin;
for (fin = start; fin < msgBuffer.length -4; fin ++) {
if ((char) msgBuffer[fin] == '\r'
&& (char) msgBuffer[fin+1] == '\n'
&& (char) msgBuffer[fin+2] == '\r'
&& (char) msgBuffer[fin+3] == '\n') {
break;
}
}
if (fin < msgBuffer.length) {
// we do not handle the (theoretically possible) case that the
// headers end with LFLF but there *is* CRLFCRLF in the body
fin += 4;
} else {
// Could not find CRLFCRLF end of message so look for LFLF
for (fin = start; fin < msgBuffer.length -2; fin++) {
if ((char)msgBuffer[fin] == '\n'
&& (char)msgBuffer[fin+1] == '\n') break;
}
if (fin < msgBuffer.length) fin += 2;
else throw new ParseException("Message not terminated", 0);
}
// Encode the body as a UTF-8 string.
String messageString = null;
try {
messageString = new String(msgBuffer, start, fin - start, "UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new ParseException("Bad message encoding!", 0);
}
bufferPointer = fin;
int length = messageString.length();
StringBuffer message = new StringBuffer(length);
// Get rid of CR to make it uniform for the parser.
for (int k = 0; k < length; k++) {
final char currChar = messageString.charAt(k);
if (currChar != '\r') {
message.append(currChar);
}
}
length = message.length();
if (Parser.debug) {
for (int k = 0; k < length; k++) {
rawMessage1 = rawMessage1 + "[" + message.charAt(k) +"]";
}
}
// The following can be written more efficiently in a single pass
// but it is somewhat complex.
StringTokenizer tokenizer = new StringTokenizer
(message.toString(), '\n');
StringBuffer cooked_message = new StringBuffer();
while (tokenizer.hasMoreChars()) {
String nexttok = tokenizer.nextToken();
// Ignore blank lines with leading spaces or tabs.
if (nexttok.trim().equals("")) cooked_message.append("\n");
else cooked_message.append(nexttok);
}
cooked_message = normalizeMessage(cooked_message);
cooked_message.append("\n\n");
// Separate the string out into substrings for
// error reporting.
currentMessage = cooked_message.toString();
Message sipmsg = parseMessage(currentMessage);
if (readBody && sipmsg.getContentLengthHeader() != null
&& sipmsg.getContentLengthHeader().getContentLength() != 0) {
contentLength = sipmsg.getContentLengthHeader().getContentLength();
byte body[] = getBodyAsBytes();
sipmsg.setMessageContent(body);
}
// System.out.println("Parsed = [" + sipmsg + "]");
return sipmsg;
}
/**
* Parses a buffer containing one or more SIP Messages and return
* an array of
* Message parsed structures. Note that the current limitation is that
* this does not handle content encoding properly. The message content is
* just assumed to be encoded using the same encoding as the sip message
* itself (i.e. binary encodings such as gzip are not supported).
* @param sipMessages a String containing the messages to be parsed.
* This can consist of multiple SIP Messages concatenated together.
* @return a Message structure (request or response)
* containing the parsed SIP message.
* @exception ParseException is thrown when an
* illegal message has been encountered (and
* the rest of the buffer is discarded).
* @see ParseExceptionListener
*/
public Message parseSIPMessage(String sipMessages)
throws ParseException {
// Handle line folding and evil DOS CR-LF sequences
rawMessage = sipMessages;
Vector retval = new Vector();
String pmessage = sipMessages;
bodyIsString = true;
this.contentLength = 0;
if (pmessage.trim().equals(""))
return null;
pmessage += "\n\n";
StringBuffer message = new StringBuffer(pmessage);
// squeeze out the leading crlf sequences.
while (message.charAt(0) == '\r' || message.charAt(0) == '\n') {
bufferPointer ++;
message.deleteCharAt(0);
}
// squeeze out the crlf sequences and make them uniformly CR
String message1 = message.toString();
int length;
length = message1.indexOf("\r\n\r\n");
if (length > 0) length += 4;
if (length == -1) {
length = message1.indexOf("\n\n");
if (length == -1)
throw new ParseException("no trailing crlf", 0);
} else length += 2;
// Get rid of CR to make it uniform.
for (int k = 0; k < length; k++) {
if (message.charAt(k) == '\r') {
message.deleteCharAt(k);
length --;
}
}
if (debugFlag) {
for (int k = 0; k < length; k++) {
rawMessage1 = rawMessage1 + "[" + message.charAt(k) +"]";
}
}
// The following can be written more efficiently in a single pass
// but it is somewhat complex.
StringTokenizer tokenizer = new StringTokenizer
(message.toString(), '\n');
StringBuffer cooked_message = new StringBuffer();
while (tokenizer.hasMoreChars()) {
String nexttok = tokenizer.nextToken();
// Ignore blank lines with leading spaces or tabs.
if (nexttok.trim().equals("")) cooked_message.append("\n");
else cooked_message.append(nexttok);
}
cooked_message = normalizeMessage(cooked_message);
cooked_message.append("\n\n");
// Separate the string out into substrings for
// error reporting.
currentMessage = cooked_message.toString();
if (Parser.debug) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
currentMessage);
}
bufferPointer = currentMessage.indexOf("\n\n") + 3;
Message sipmsg = this.parseMessage(currentMessage);
if (readBody && sipmsg.getContentLengthHeader() != null &&
sipmsg.getContentLengthHeader().getContentLength() != 0) {
this.contentLength =
sipmsg.getContentLengthHeader().getContentLength();
String body = this.getMessageBody();
sipmsg.setMessageContent(body);
}
return sipmsg;
}
/**
* Normalize message string, i.e. remove whitespace
* @param srcMsg message to be processed
* @return normalized message
*/
private StringBuffer normalizeMessage(StringBuffer srcMsg) {
StringBuffer normalizedMessage = new StringBuffer(srcMsg.length());
String message1 = srcMsg.toString();
int length = message1.indexOf("\n\n") + 2;
int k = 0;
while (k < length - 1) {
final char thisChar = srcMsg.charAt(k);
final char thatChar = srcMsg.charAt(k+1);
// is it a continuation line?
if (thisChar == '\n' && (thatChar == '\t' || thatChar == ' ')) {
normalizedMessage.append(' ');
k++; // skipping \n
// now remove whitespace
char nextChar;
do {
k++; // skipping \t or space
if (k == length) {
break;
}
nextChar = srcMsg.charAt(k);
} while (nextChar == ' ' || nextChar == '\t');
} else {
normalizedMessage.append(thisChar);
k++;
}
}
return normalizedMessage;
}
/**
* This is called repeatedly by parseMessage to parse
* the contents of a message buffer. This assumes the message
* already has continuations etc. taken care of.
* prior to its being called.
* @param currentMessage current message to process
* @return parsed message data
*/
private Message parseMessage(String currentMessage)
throws ParseException {
// position line counter at the end of the
// sip messages.
//
// System.out.println("parsing <<" + currentMessage+">>");
Message sipmsg = null;
StringTokenizer tokenizer = new StringTokenizer(currentMessage, '\n');
messageHeaders = new Vector(); // A list of headers for error reporting
while (tokenizer.hasMoreChars()) {
String nexttok = tokenizer.nextToken();
if (nexttok.equals("\n")) {
String nextnexttok = tokenizer.nextToken();
if (nextnexttok.equals("\n")) {
break;
} else messageHeaders.addElement(nextnexttok);
} else messageHeaders.addElement(nexttok);
}
currentLine = 0;
currentHeader = (String) messageHeaders.elementAt(currentLine);
String firstLine = currentHeader;
// System.out.println("first Line " + firstLine);
if (!firstLine.startsWith(SIPConstants.SIP_VERSION_STRING)) {
sipmsg = new Request();
try {
RequestLine rl =
new RequestLineParser(firstLine+ "\n").parse();
((Request) sipmsg).setRequestLine(rl);
} catch (ParseException ex) {
if (this.parseExceptionListener != null)
this.parseExceptionListener.handleException
(ex, sipmsg, new RequestLine().getClass(),
firstLine, currentMessage);
else throw ex;
}
} else {
sipmsg = new Response();
try {
StatusLine sl = new StatusLineParser(firstLine + "\n").parse();
((Response) sipmsg).setStatusLine(sl);
} catch (ParseException ex) {
if (this.parseExceptionListener != null) {
this.parseExceptionListener.handleException
(ex, sipmsg,
new StatusLine().getClass(),
firstLine, currentMessage);
} else throw ex;
}
}
for (int i = 1; i < messageHeaders.size(); i++) {
String hdrstring = (String) messageHeaders.elementAt(i);
if (hdrstring == null || hdrstring.trim().equals("")) {
continue;
}
HeaderParser hdrParser = null;
try {
// System.out.println("'" + hdrstring + "'");
hdrParser = ParserFactory.createParser(hdrstring + "\n");
} catch (ParseException ex) {
parseExceptionListener.handleException(
ex, sipmsg, null, hdrstring, currentMessage);
continue;
}
Header sipHeader = null;
try {
sipHeader = hdrParser.parse();
sipmsg.attachHeader(sipHeader, false);
} catch (ParseException ex) {
// ex.printStackTrace();
if (parseExceptionListener != null) {
String hdrName = Lexer.getHeaderName(hdrstring);
Class hdrClass = NameMap.getClassFromName(hdrName);
if (hdrClass == null) {
hdrClass = ExtensionHeader.clazz;
}
parseExceptionListener.handleException(
ex, sipmsg, hdrClass, hdrstring, currentMessage);
} else { // use generic parser
hdrParser = new ExtensionParser(hdrstring + "\n");
sipHeader = hdrParser.parse();
try {
sipmsg.attachHeader(sipHeader, false);
} catch (SipException exc) {
throw new ParseException(sipHeader.toString(), 0);
}
}
} catch (SipException ex) {
// Invalid header.
throw new ParseException(sipHeader.toString(), 0);
}
}
return sipmsg;
}
/**
* Parses an address (nameaddr or address spec) and return and address
* structure.
* @param address is a String containing the address to be parsed.
* @return a parsed address structure.
* @since v1.0
* @exception ParseException when the address is badly formatted.
*/
public Address parseAddress(String address)
throws ParseException {
AddressParser addressParser = new AddressParser(address);
return addressParser.address();
}
/**
* Parses a host:port and return a parsed structure.
* @param hostport is a String containing the host:port to be parsed
* @return a parsed address structure.
* @since v1.0
* @exception ParseException when the address is badly formatted.
*/
public HostPort parseHostPort(String hostport)
throws ParseException {
Lexer lexer = new Lexer("charLexer", hostport);
return new HostNameParser(lexer).hostPort();
}
/**
* Parse a host name and return a parsed structure.
* @param host is a String containing the host name to be parsed
* @return a parsed address structure.
* @since v1.0
* @exception ParseException when the hostname is badly formatted.
*/
public Host parseHost(String host)
throws ParseException {
Lexer lexer = new Lexer("charLexer", host);
HostNameParser hp = new HostNameParser(lexer);
return new Host(hp.hostName());
}
/**
* Parses a telephone number return a parsed structure.
* @param telephone_number is a String containing the
* telephone # to be parsed
* @return a parsed address structure.
* @since v1.0
* @exception ParseException when the address is badly formatted.
*/
public TelephoneNumber parseTelephoneNumber(String telephone_number)
throws ParseException {
return new URLParser(telephone_number).parseTelephoneNumber();
}
/**
* Parses a SIP url from a string and return a URI structure for it.
* @param url a String containing the URI structure to be parsed.
* @return A parsed URI structure
* @exception ParseException if there was an error parsing the message.
*/
public SipURI parseSIPUrl(String url)
throws ParseException {
try {
URLParser parser = new URLParser(url);
SipURI uri = (SipURI)parser.parse();
// whole string has to be consumed
// otherwise it is wrong URL or not URL only
if (parser.getLexer().hasMoreChars()) {
throw new ParseException(url + " Not a URL string",
parser.getLexer().getPtr());
}
return uri;
} catch (ClassCastException ex) {
throw new ParseException(url + " Not a SIP URL ", 0);
}
}
/**
* Parses a uri from a string and return a URI structure for it.
* @param url a String containing the URI structure to be parsed.
* @return A parsed URI structure
* @exception ParseException if there was an error parsing the message.
*/
public URI parseUrl(String url)
throws ParseException {
return new URLParser(url).parse();
}
/**
* Parses an individual SIP message header from a string.
* @param header String containing the SIP header.
* @return a Header structure.
* @exception ParseException if there was an error parsing the message.
*/
public Header parseHeader(String header)
throws ParseException {
// It's not clear why "\n\n" was added to the header.
// header += "\n\n";
// Handle line folding.
String nmessage = StringTokenizer.convertNewLines(header);
nmessage += "\n"; /* why not Separators.NEWLINE ? */
// System.out.println(">>> '" + nmessage + "'");
HeaderParser hp = ParserFactory.createParser(nmessage);
if (hp == null)
throw new ParseException("could not create parser", 0);
return hp.parse();
}
/**
* Parses the SIP Request Line
* @param requestLine a String containing the request line to be parsed.
* @return a RequestLine structure that has the parsed RequestLine
* @exception ParseException if there was an error parsing the requestLine.
*/
public RequestLine parseRequestLine(String requestLine)
throws ParseException {
requestLine += "\n";
return new RequestLineParser(requestLine).parse();
}
/**
* Parses the SIP Response message status line
* @param statusLine a String containing the Status line to be parsed.
* @return StatusLine class corresponding to message
* @exception ParseException if there was an error parsing
* @see StatusLine
*/
public StatusLine parseSIPStatusLine(String statusLine)
throws ParseException {
statusLine += "\n";
return new StatusLineParser(statusLine).parse();
}
/**
* Gets the current header.
* @return the current header
*/
public String getCurrentHeader() {
return currentHeader;
}
/**
* Gets the current line number.
* @return the current line number
*/
public int getCurrentLineNumber() {
return currentLine;
}
}
|