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 
31 package gov.nist.javax.sip.parser;
32 
33 import gov.nist.core.Host;
34 import gov.nist.core.HostNameParser;
35 import gov.nist.javax.sip.SIPConstants;
36 import gov.nist.javax.sip.address.AddressImpl;
37 import gov.nist.javax.sip.address.GenericURI;
38 import gov.nist.javax.sip.address.SipUri;
39 import gov.nist.javax.sip.address.TelephoneNumber;
40 import gov.nist.javax.sip.header.*;
41 import gov.nist.javax.sip.message.SIPMessage;
42 import gov.nist.javax.sip.message.SIPRequest;
43 import gov.nist.javax.sip.message.SIPResponse;
44 
45 import java.io.UnsupportedEncodingException;
46 import 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  */
73 public 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      */
StringMsgParser()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      */
StringMsgParser(ParseExceptionListener exhandler)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      */
setParseExceptionListener(ParseExceptionListener pexhandler)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      */
parseSIPMessage(byte[] msgBuffer)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      */
parseSIPMessage(String msgString)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 
trimEndOfLine(String line)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 
processFirstLine(String firstLine)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 
processHeader(String header, SIPMessage message)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      */
parseAddress(String address)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      */
parseHost(String host)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      */
parseTelephoneNumber(String telephone_number)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 
parseSIPUrl(String url)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 
parseUrl(String url)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      */
parseSIPHeader(String header)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 
parseSIPRequestLine(String requestLine)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 
parseSIPStatusLine(String statusLine)608     public StatusLine parseSIPStatusLine(String statusLine)
609             throws ParseException {
610         statusLine += "\n";
611         return new StatusLineParser(statusLine).parse();
612     }
613 
setComputeContentLengthFromMessage( boolean computeContentLengthFromMessage)614     public static void setComputeContentLengthFromMessage(
615             boolean computeContentLengthFromMessage) {
616         StringMsgParser.computeContentLengthFromMessage = computeContentLengthFromMessage;
617     }
618 
619 
620 
621     /**
622      * Test code.
623      */
main(String[] args)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 
setStrict(boolean strict)704     public void setStrict(boolean strict) {
705        this.strict = strict;
706 
707     }
708 
709 }
710