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 */ 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 (at) there.com>;tag=469bc066\r\n" 628 + "From: \"The Master Blaster\" <sip:BigGuy (at) 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 (at) 212.209.220.131>;tag=2600\r\n" 638 + "From: \"Andreas\" <sip:andreas (at) e-horizon.se>;tag=8524\r\n" 639 + "Call-ID: f51a1851c5f570606140f14c8eb64fd3 (at) 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 (at) nist.gov>;tag=6fcd5c7ace8b4a45acf0f0cd539b168b;epid=0d4c418ddf\r\n" 647 + "To: <sip:mranga (at) nist.gov>\r\n" 648 + "Call-ID: c5679907eb954a8da9f9dceb282d7230 (at) 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 (at) 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 (at) sipbakeoff.com>;tag=1234\r\n" 658 + "To: \"littleguy (at) there.com\" <sip:littleguy (at) there.com:5060> \r\n" 659 + "Call-ID: Q2AboBsaGn9!?x6 (at) 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