FileDocCategorySizeDatePackage
StringMsgParser.javaAPI DocphoneME MR2 API (J2ME)26233Wed May 02 18:00:42 BST 2007gov.nist.siplite.parser

StringMsgParser.java

/*
 * 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;
    }
}