1/*
2 * Conditions Of Use
3 *
4 * This software was developed by employees of the National Institute of
5 * Standards and Technology (NIST), an agency of the Federal Government.
6 * Pursuant to title 15 Untied States Code Section 105, works of NIST
7 * employees are not subject to copyright protection in the United States
8 * and are considered to be in the public domain.  As a result, a formal
9 * license is not needed to use the software.
10 *
11 * This software is provided by NIST as a service and is expressly
12 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
13 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
14 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
15 * AND DATA ACCURACY.  NIST does not warrant or make any representations
16 * regarding the use of the software or the results thereof, including but
17 * not limited to the correctness, accuracy, reliability or usefulness of
18 * the software.
19 *
20 * Permission to use this software is contingent upon your acceptance
21 * of the terms of this agreement
22 *
23 * .
24 *
25 */
26
27/*******************************************************************************
28 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD)        *
29 ******************************************************************************/
30
31package gov.nist.javax.sip.parser;
32
33import gov.nist.core.Host;
34import gov.nist.core.HostNameParser;
35import gov.nist.javax.sip.SIPConstants;
36import gov.nist.javax.sip.address.AddressImpl;
37import gov.nist.javax.sip.address.GenericURI;
38import gov.nist.javax.sip.address.SipUri;
39import gov.nist.javax.sip.address.TelephoneNumber;
40import gov.nist.javax.sip.header.*;
41import gov.nist.javax.sip.message.SIPMessage;
42import gov.nist.javax.sip.message.SIPRequest;
43import gov.nist.javax.sip.message.SIPResponse;
44
45import java.io.UnsupportedEncodingException;
46import java.text.ParseException;
47/*
48 * Acknowledgement: 1/12/2007: Yanick Belanger rewrote the parsing loops to make them
49 * simpler and quicker.
50 */
51
52/**
53 * Parse SIP message and parts of SIP messages such as URI's etc from memory and
54 * return a structure. Intended use: UDP message processing. This class is used
55 * when you have an entire SIP message or SIPHeader or SIP URL in memory and you
56 * want to generate a parsed structure from it. For SIP messages, the payload
57 * can be binary or String. If you have a binary payload, use
58 * parseSIPMessage(byte[]) else use parseSIPMessage(String) The payload is
59 * accessible from the parsed message using the getContent and getContentBytes
60 * methods provided by the SIPMessage class. If SDP parsing is enabled using the
61 * parseContent method, then the SDP body is also parsed and can be accessed
62 * from the message using the getSDPAnnounce method. Currently only eager
63 * parsing of the message is supported (i.e. the entire message is parsed in one
64 * feld swoop).
65 *
66 *
67 * @version 1.2 $Revision: 1.26 $ $Date: 2009/10/22 10:27:38 $
68 *
69 * @author M. Ranganathan <br/>
70 *
71  *
72 */
73public class StringMsgParser {
74
75    protected boolean readBody;
76    private ParseExceptionListener parseExceptionListener;
77    private String rawStringMessage;
78    private boolean strict;
79
80    private static boolean computeContentLengthFromMessage = false;
81
82    /**
83     * @since v0.9
84     */
85    public StringMsgParser() {
86        super();
87        readBody = true;
88    }
89
90    /**
91     * Constructor (given a parse exception handler).
92     *
93     * @since 1.0
94     * @param exhandler
95     *            is the parse exception listener for the message parser.
96     */
97    public StringMsgParser(ParseExceptionListener exhandler) {
98        this();
99        parseExceptionListener = exhandler;
100    }
101
102    /**
103     * Add a handler for header parsing errors.
104     *
105     * @param pexhandler
106     *            is a class that implements the ParseExceptionListener
107     *            interface.
108     */
109    public void setParseExceptionListener(ParseExceptionListener pexhandler) {
110        parseExceptionListener = pexhandler;
111    }
112
113    /**
114     * Parse a buffer containing a single SIP Message where the body is an array
115     * of un-interpreted bytes. This is intended for parsing the message from a
116     * memory buffer when the buffer. Incorporates a bug fix for a bug that was
117     * noted by Will Sullin of Callcast
118     *
119     * @param msgBuffer
120     *            a byte buffer containing the messages to be parsed. This can
121     *            consist of multiple SIP Messages concatenated together.
122     * @return a SIPMessage[] structure (request or response) containing the
123     *         parsed SIP message.
124     * @exception ParseException
125     *                is thrown when an illegal message has been encountered
126     *                (and the rest of the buffer is discarded).
127     * @see ParseExceptionListener
128     */
129    public SIPMessage parseSIPMessage(byte[] msgBuffer) throws ParseException {
130        if (msgBuffer == null || msgBuffer.length == 0)
131            return null;
132
133        int i = 0;
134
135        // Squeeze out any leading control character.
136        try {
137            while (msgBuffer[i] < 0x20)
138                i++;
139        }
140        catch (ArrayIndexOutOfBoundsException e) {
141            // Array contains only control char, return null.
142            return null;
143        }
144
145        // Iterate thru the request/status line and headers.
146        String currentLine = null;
147        String currentHeader = null;
148        boolean isFirstLine = true;
149        SIPMessage message = null;
150        do
151        {
152            int lineStart = i;
153
154            // Find the length of the line.
155            try {
156                while (msgBuffer[i] != '\r' && msgBuffer[i] != '\n')
157                    i++;
158            }
159            catch (ArrayIndexOutOfBoundsException e) {
160                // End of the message.
161                break;
162            }
163            int lineLength = i - lineStart;
164
165            // Make it a String.
166            try {
167                currentLine = new String(msgBuffer, lineStart, lineLength, "UTF-8");
168            } catch (UnsupportedEncodingException e) {
169                throw new ParseException("Bad message encoding!", 0);
170            }
171
172            currentLine = trimEndOfLine(currentLine);
173
174            if (currentLine.length() == 0) {
175                // Last header line, process the previous buffered header.
176                if (currentHeader != null && message != null) {
177                     processHeader(currentHeader, message);
178                 }
179
180            }
181            else {
182                if (isFirstLine) {
183                    message = processFirstLine(currentLine);
184                } else {
185                    char firstChar = currentLine.charAt(0);
186                    if (firstChar == '\t' || firstChar == ' ') {
187                        if (currentHeader == null)
188                            throw new ParseException("Bad header continuation.", 0);
189
190                        // This is a continuation, append it to the previous line.
191                        currentHeader += currentLine.substring(1);
192                    }
193                    else {
194                        if (currentHeader != null && message != null) {
195                             processHeader(currentHeader, message);
196                         }
197                        currentHeader = currentLine;
198                    }
199                }
200            }
201
202            if (msgBuffer[i] == '\r' && msgBuffer.length > i+1 && msgBuffer[i+1] == '\n')
203                i++;
204
205            i++;
206
207            isFirstLine = false;
208        } while (currentLine.length() > 0); // End do - while
209
210        if (message == null) throw new ParseException("Bad message", 0);
211        message.setSize(i);
212
213        if (readBody && message.getContentLength() != null &&
214                message.getContentLength().getContentLength() != 0) {
215
216            int bodyLength = msgBuffer.length - i;
217
218            byte[] body = new byte[bodyLength];
219            System.arraycopy(msgBuffer, i, body, 0, bodyLength);
220            message.setMessageContent(body,computeContentLengthFromMessage ,message.getContentLength().getContentLength() );
221        }
222
223        return message;
224    }
225
226    /**
227     * Parse a buffer containing one or more SIP Messages and return an array of
228     * SIPMessage parsed structures.
229     *
230     * @param msgString
231     *            a String containing the messages to be parsed. This can
232     *            consist of multiple SIP Messages concatenated together.
233     * @return a SIPMessage structure (request or response) containing the
234     *         parsed SIP message.
235     * @exception ParseException
236     *                is thrown when an illegal message has been encountered
237     *                (and the rest of the buffer is discarded).
238     * @see ParseExceptionListener
239     */
240    public SIPMessage parseSIPMessage(String msgString) throws ParseException {
241        if (msgString == null || msgString.length() == 0)
242            return null;
243
244        rawStringMessage = msgString;
245
246        int i = 0;
247
248        // Squeeze out any leading control character.
249        try {
250            while (msgString.charAt(i) < 0x20)
251                i++;
252        }
253        catch (ArrayIndexOutOfBoundsException e) {
254            // Array contains only control char, return null.
255            return null;
256        } catch (StringIndexOutOfBoundsException ex) {
257            return null;
258        }
259
260        // Iterate thru the request/status line and headers.
261        String currentLine = null;
262        String currentHeader = null;
263        boolean isFirstLine = true;
264        SIPMessage message = null;
265        do
266        {
267            int lineStart = i;
268
269            // Find the length of the line.
270            try {
271                char c = msgString.charAt(i);
272                while (c != '\r' && c != '\n')
273                    c = msgString.charAt(++i);
274            }
275            catch (ArrayIndexOutOfBoundsException e) {
276                // End of the message.
277                break;
278            } catch ( StringIndexOutOfBoundsException ex) {
279                break;
280            }
281
282            // Make it a String.
283            currentLine = msgString.substring(lineStart, i);
284            currentLine = trimEndOfLine(currentLine);
285
286            if (currentLine.length() == 0) {
287                // Last header line, process the previous buffered header.
288                if (currentHeader != null) {
289                    processHeader(currentHeader, message);
290                }
291            }
292            else {
293                if (isFirstLine) {
294                    message = processFirstLine(currentLine);
295                } else {
296                    char firstChar = currentLine.charAt(0);
297                    if (firstChar == '\t' || firstChar == ' ') {
298                        if (currentHeader == null)
299                            throw new ParseException("Bad header continuation.", 0);
300
301                        // This is a continuation, append it to the previous line.
302                        currentHeader += currentLine.substring(1);
303                    }
304                    else {
305                        if (currentHeader != null) {
306                            processHeader(currentHeader, message);
307                        }
308                        currentHeader = currentLine;
309                    }
310                }
311            }
312
313            if (msgString.charAt(i) == '\r' && msgString.length() > i+1 && msgString.charAt(i+1) == '\n')
314                i++;
315
316            i++;
317
318            isFirstLine = false;
319        }
320        while (currentLine.length() > 0);
321
322        message.setSize(i);
323
324        // Check for content legth header
325        if (readBody && message.getContentLength() != null ) {
326            if ( message.getContentLength().getContentLength() != 0) {
327                String body = msgString.substring(i);
328                message.setMessageContent(body,this.strict,computeContentLengthFromMessage,message.getContentLength().getContentLength());
329             } else if (!computeContentLengthFromMessage && message.getContentLength().getContentLength() == 0 && !msgString.endsWith("\r\n\r\n") ){
330                 if ( strict ) {
331                     throw new ParseException("Extraneous characters at the end of the message ",i);
332                 }
333             }
334
335        }
336
337        return message;
338    }
339
340    private String trimEndOfLine(String line) {
341        if (line == null)
342            return line;
343
344        int i = line.length() - 1;
345        while (i >= 0 && line.charAt(i) <= 0x20)
346            i--;
347
348        if (i == line.length() - 1)
349            return line;
350
351        if (i == -1)
352            return "";
353
354        return line.substring(0, i+1);
355    }
356
357    private SIPMessage processFirstLine(String firstLine) throws ParseException {
358        SIPMessage message;
359        if (!firstLine.startsWith(SIPConstants.SIP_VERSION_STRING)) {
360            message = new SIPRequest();
361            try {
362                RequestLine requestLine = new RequestLineParser(firstLine + "\n")
363                        .parse();
364                ((SIPRequest) message).setRequestLine(requestLine);
365            } catch (ParseException ex) {
366                if (this.parseExceptionListener != null)
367                    this.parseExceptionListener.handleException(ex, message,
368                            RequestLine.class, firstLine, rawStringMessage);
369                else
370                    throw ex;
371
372            }
373        } else {
374            message = new SIPResponse();
375            try {
376                StatusLine sl = new StatusLineParser(firstLine + "\n").parse();
377                ((SIPResponse) message).setStatusLine(sl);
378            } catch (ParseException ex) {
379                if (this.parseExceptionListener != null) {
380                    this.parseExceptionListener.handleException(ex, message,
381                            StatusLine.class, firstLine, rawStringMessage);
382                } else
383                    throw ex;
384
385            }
386        }
387        return message;
388    }
389
390    private void processHeader(String header, SIPMessage message) throws ParseException {
391        if (header == null || header.length() == 0)
392            return;
393
394        HeaderParser headerParser = null;
395        try {
396            headerParser = ParserFactory.createParser(header + "\n");
397        } catch (ParseException ex) {
398            this.parseExceptionListener.handleException(ex, message, null,
399                    header, rawStringMessage);
400            return;
401        }
402
403        try {
404            SIPHeader sipHeader = headerParser.parse();
405            message.attachHeader(sipHeader, false);
406        } catch (ParseException ex) {
407            if (this.parseExceptionListener != null) {
408                String headerName = Lexer.getHeaderName(header);
409                Class headerClass = NameMap.getClassFromName(headerName);
410                if (headerClass == null) {
411                    headerClass = ExtensionHeaderImpl.class;
412
413                }
414                this.parseExceptionListener.handleException(ex, message,
415                        headerClass, header, rawStringMessage);
416
417            }
418        }
419    }
420
421    /**
422     * Parse an address (nameaddr or address spec) and return and address
423     * structure.
424     *
425     * @param address
426     *            is a String containing the address to be parsed.
427     * @return a parsed address structure.
428     * @since v1.0
429     * @exception ParseException
430     *                when the address is badly formatted.
431     */
432    public AddressImpl parseAddress(String address) throws ParseException {
433        AddressParser addressParser = new AddressParser(address);
434        return addressParser.address(true);
435    }
436
437    /**
438     * Parse a host:port and return a parsed structure.
439     *
440     * @param hostport
441     *            is a String containing the host:port to be parsed
442     * @return a parsed address structure.
443     * @since v1.0
444     * @exception throws
445     *                a ParseException when the address is badly formatted.
446     *
447    public HostPort parseHostPort(String hostport) throws ParseException {
448        Lexer lexer = new Lexer("charLexer", hostport);
449        return new HostNameParser(lexer).hostPort();
450
451    }
452    */
453
454    /**
455     * Parse a host name and return a parsed structure.
456     *
457     * @param host
458     *            is a String containing the host name to be parsed
459     * @return a parsed address structure.
460     * @since v1.0
461     * @exception ParseException
462     *                a ParseException when the hostname is badly formatted.
463     */
464    public Host parseHost(String host) throws ParseException {
465        Lexer lexer = new Lexer("charLexer", host);
466        return new HostNameParser(lexer).host();
467
468    }
469
470    /**
471     * Parse a telephone number return a parsed structure.
472     *
473     * @param telephone_number
474     *            is a String containing the telephone # to be parsed
475     * @return a parsed address structure.
476     * @since v1.0
477     * @exception ParseException
478     *                a ParseException when the address is badly formatted.
479     */
480    public TelephoneNumber parseTelephoneNumber(String telephone_number)
481            throws ParseException {
482        // Bug fix contributed by Will Scullin
483        return new URLParser(telephone_number).parseTelephoneNumber(true);
484
485    }
486
487    /**
488     * Parse a SIP url from a string and return a URI structure for it.
489     *
490     * @param url
491     *            a String containing the URI structure to be parsed.
492     * @return A parsed URI structure
493     * @exception ParseException
494     *                if there was an error parsing the message.
495     */
496
497    public SipUri parseSIPUrl(String url) throws ParseException {
498        try {
499            return new URLParser(url).sipURL(true);
500        } catch (ClassCastException ex) {
501            throw new ParseException(url + " Not a SIP URL ", 0);
502        }
503    }
504
505    /**
506     * Parse a uri from a string and return a URI structure for it.
507     *
508     * @param url
509     *            a String containing the URI structure to be parsed.
510     * @return A parsed URI structure
511     * @exception ParseException
512     *                if there was an error parsing the message.
513     */
514
515    public GenericURI parseUrl(String url) throws ParseException {
516        return new URLParser(url).parse();
517    }
518
519    /**
520     * Parse an individual SIP message header from a string.
521     *
522     * @param header
523     *            String containing the SIP header.
524     * @return a SIPHeader structure.
525     * @exception ParseException
526     *                if there was an error parsing the message.
527     */
528    public SIPHeader parseSIPHeader(String header) throws ParseException {
529        int start = 0;
530        int end = header.length() - 1;
531        try {
532            // Squeeze out any leading control character.
533            while (header.charAt(start) <= 0x20)
534                start++;
535
536            // Squeeze out any trailing control character.
537            while (header.charAt(end) <= 0x20)
538                end--;
539        }
540        catch (ArrayIndexOutOfBoundsException e) {
541            // Array contains only control char.
542            throw new ParseException("Empty header.", 0);
543        }
544
545        StringBuffer buffer = new StringBuffer(end + 1);
546        int i = start;
547        int lineStart = start;
548        boolean endOfLine = false;
549        while (i <= end) {
550            char c = header.charAt(i);
551            if (c == '\r' || c == '\n') {
552                if (!endOfLine) {
553                    buffer.append(header.substring(lineStart, i));
554                    endOfLine = true;
555                }
556            }
557            else {
558                if (endOfLine) {
559                    endOfLine = false;
560                    if (c == ' ' || c == '\t') {
561                        buffer.append(' ');
562                        lineStart = i + 1;
563                    }
564                    else {
565                        lineStart = i;
566                    }
567                }
568            }
569
570            i++;
571        }
572        buffer.append(header.substring(lineStart, i));
573        buffer.append('\n');
574
575        HeaderParser hp = ParserFactory.createParser(buffer.toString());
576        if (hp == null)
577            throw new ParseException("could not create parser", 0);
578        return hp.parse();
579    }
580
581    /**
582     * Parse the SIP Request Line
583     *
584     * @param requestLine
585     *            a String containing the request line to be parsed.
586     * @return a RequestLine structure that has the parsed RequestLine
587     * @exception ParseException
588     *                if there was an error parsing the requestLine.
589     */
590
591    public RequestLine parseSIPRequestLine(String requestLine)
592            throws ParseException {
593        requestLine += "\n";
594        return new RequestLineParser(requestLine).parse();
595    }
596
597    /**
598     * Parse the SIP Response message status line
599     *
600     * @param statusLine
601     *            a String containing the Status line to be parsed.
602     * @return StatusLine class corresponding to message
603     * @exception ParseException
604     *                if there was an error parsing
605     * @see StatusLine
606     */
607
608    public StatusLine parseSIPStatusLine(String statusLine)
609            throws ParseException {
610        statusLine += "\n";
611        return new StatusLineParser(statusLine).parse();
612    }
613
614    public static void setComputeContentLengthFromMessage(
615            boolean computeContentLengthFromMessage) {
616        StringMsgParser.computeContentLengthFromMessage = computeContentLengthFromMessage;
617    }
618
619
620
621    /**
622     * Test code.
623     */
624    public static void main(String[] args) throws ParseException {
625        String messages[] = {
626                "SIP/2.0 200 OK\r\n"
627                        + "To: \"The Little Blister\" <sip:LittleGuy@there.com>;tag=469bc066\r\n"
628                        + "From: \"The Master Blaster\" <sip:BigGuy@here.com>;tag=11\r\n"
629                        + "Via: SIP/2.0/UDP 139.10.134.246:5060;branch=z9hG4bK8b0a86f6_1030c7d18e0_17;received=139.10.134.246\r\n"
630                        + "Call-ID: 1030c7d18ae_a97b0b_b@8b0a86f6\r\n"
631                        + "CSeq: 1 SUBSCRIBE\r\n"
632                        + "Contact: <sip:172.16.11.162:5070>\r\n"
633                        + "Content-Length: 0\r\n\r\n",
634
635                "SIP/2.0 180 Ringing\r\n"
636                        + "Via: SIP/2.0/UDP 172.18.1.29:5060;branch=z9hG4bK43fc10fb4446d55fc5c8f969607991f4\r\n"
637                        + "To: \"0440\" <sip:0440@212.209.220.131>;tag=2600\r\n"
638                        + "From: \"Andreas\" <sip:andreas@e-horizon.se>;tag=8524\r\n"
639                        + "Call-ID: f51a1851c5f570606140f14c8eb64fd3@172.18.1.29\r\n"
640                        + "CSeq: 1 INVITE\r\n" + "Max-Forwards: 70\r\n"
641                        + "Record-Route: <sip:212.209.220.131:5060>\r\n"
642                        + "Content-Length: 0\r\n\r\n",
643                "REGISTER sip:nist.gov SIP/2.0\r\n"
644                        + "Via: SIP/2.0/UDP 129.6.55.182:14826\r\n"
645                        + "Max-Forwards: 70\r\n"
646                        + "From: <sip:mranga@nist.gov>;tag=6fcd5c7ace8b4a45acf0f0cd539b168b;epid=0d4c418ddf\r\n"
647                        + "To: <sip:mranga@nist.gov>\r\n"
648                        + "Call-ID: c5679907eb954a8da9f9dceb282d7230@129.6.55.182\r\n"
649                        + "CSeq: 1 REGISTER\r\n"
650                        + "Contact: <sip:129.6.55.182:14826>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER\"\r\n"
651                        + "User-Agent: RTC/(Microsoft RTC)\r\n"
652                        + "Event:  registration\r\n"
653                        + "Allow-Events: presence\r\n"
654                        + "Content-Length: 0\r\n\r\n"
655                        + "INVITE sip:littleguy@there.com:5060 SIP/2.0\r\n"
656                        + "Via: SIP/2.0/UDP 65.243.118.100:5050\r\n"
657                        + "From: M. Ranganathan  <sip:M.Ranganathan@sipbakeoff.com>;tag=1234\r\n"
658                        + "To: \"littleguy@there.com\" <sip:littleguy@there.com:5060> \r\n"
659                        + "Call-ID: Q2AboBsaGn9!?x6@sipbakeoff.com \r\n"
660                        + "CSeq: 1 INVITE \r\n"
661                        + "Content-Length: 247\r\n\r\n"
662                        + "v=0\r\n"
663                        + "o=4855 13760799956958020 13760799956958020 IN IP4  129.6.55.78\r\n"
664                        + "s=mysession session\r\n" + "p=+46 8 52018010\r\n"
665                        + "c=IN IP4  129.6.55.78\r\n" + "t=0 0\r\n"
666                        + "m=audio 6022 RTP/AVP 0 4 18\r\n"
667                        + "a=rtpmap:0 PCMU/8000\r\n"
668                        + "a=rtpmap:4 G723/8000\r\n"
669                        + "a=rtpmap:18 G729A/8000\r\n" + "a=ptime:20\r\n" };
670
671        class ParserThread implements Runnable {
672            String[] messages;
673
674            public ParserThread(String[] messagesToParse) {
675                this.messages = messagesToParse;
676            }
677
678            public void run() {
679                for (int i = 0; i < messages.length; i++) {
680                    StringMsgParser smp = new StringMsgParser();
681                    try {
682                        SIPMessage sipMessage = smp
683                                .parseSIPMessage(messages[i]);
684                        System.out.println(" i = " + i + " branchId = "
685                                + sipMessage.getTopmostVia().getBranch());
686                        // System.out.println("encoded " +
687                        // sipMessage.toString());
688                    } catch (ParseException ex) {
689
690                    }
691
692                    // System.out.println("dialog id = " +
693                    // sipMessage.getDialogId(false));
694                }
695            }
696        }
697
698        for (int i = 0; i < 20; i++) {
699            new Thread(new ParserThread(messages)).start();
700        }
701
702    }
703
704    public void setStrict(boolean strict) {
705       this.strict = strict;
706
707    }
708
709}
710