Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

closes RestComm/jain-slee#111 #149

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/gov/nist/core/LexerCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,8 @@ public String number() throws ParseException {

int startIdx = ptr;
try {
if (!isDigit(lookAhead(0))) {
char c = lookAhead(0);
if (c != '-' && !isDigit(c)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic will affect several parsing scenarios. What is the reasoning to include the condition on "-" char? is it based on any RFC3261 statement? can you include a link to the grammar that supports this rule?

Copy link
Contributor Author

@ThE-MaRaC ThE-MaRaC May 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is based on the following RFC: https://tools.ietf.org/html/rfc4475#section-3.3.9
RFC states:
From a framing perspective, this situation is equivalent to an invalid Content-Length value (or no value at all).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you include this link,and brief explanation as comment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my concern is that this requirement seems to be specific for Content-Length header, but this piece of code we are modifying is applied to all sort of numberic header conversions.., hence im afraid we can break contract for other headers...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
this a brief of main usages for "number" method including different types of headers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently, if I rewrite the test as in (i prefer my version,not based on asserting exception message, but whether exception hs been raised or not)9:
// https://github.com/RestComm/jain-slee/issues/111 public void testNegativeContentLength(){ try { final String header = "Content-Length: -123 \n"; System.out.print(header); createParser(ContentLengthParser.class, header).parse(); fail("previous command should have raise an expection"); } catch (java.text.ParseException ex) { //expected exception thrown } }

Even the original code passes, throwing the parseexception (at different part of the logic).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All JUnit parser tests (jain-sip/src/test/unit/gov/nist/javax/sip/parser) are OK.

From the headers above the following are covered:

  • HostNameParserTest
  • TimeStampParserTest
  • ContentLengthParserTest
  • CSeqParserTest
  • MaxForwardsParserTest
  • MimeVersionParserTest
  • MinExpiresParserTest
  • RetryAfterParserTest

TCK regression is also OK.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about my claim? "Even the original code passes, throwing the parseexception (at different part of the logic)."

throw new ParseException(
buffer + ": Unexpected token at " + lookAhead(0),
ptr);
Expand Down
22 changes: 20 additions & 2 deletions src/gov/nist/javax/sip/SipProviderImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import javax.sip.TransactionUnavailableException;
import javax.sip.address.Hop;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ToHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

Expand Down Expand Up @@ -491,8 +492,25 @@ public ServerTransaction getNewServerTransaction(Request request)
SIPRequest sipRequest = (SIPRequest) request;
try {
sipRequest.checkHeaders();
} catch (ParseException ex) {
throw new TransactionUnavailableException(ex.getMessage(), ex);
} catch (ParseException pe) {
try {
SIPResponse badReqRes = sipRequest.createResponse(Response.BAD_REQUEST,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you tested this somehow? could you add ad test case for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the bug report (RestComm/jain-slee#111) there is a SIPP TC which was used to test this correction, but I will additionally create junit TC

"Bad Request (" + pe.getLocalizedMessage() + ')');
// Should add a to-tag if not already present...
ToHeader to = (ToHeader) badReqRes.getHeader(ToHeader.NAME);
if (to.getTag() == null || to.getTag().isEmpty()) {
to.setTag("badreq");
badReqRes.setHeader(to);
}
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
logger.logDebug("Sending automatic 400 Bad Request:");
logger.logDebug(badReqRes.toString());
}
sendResponse(badReqRes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a Reason-Phrase header with the exception message?

Copy link
Contributor Author

@ThE-MaRaC ThE-MaRaC May 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation is based on MessageChannel.createBadReqRes, so if we plan to add it, it should be added everywhere
For Reason header, we can use the same exception message that is used in response:
SIPResponse badReqRes = sipRequest.createResponse(Response.BAD_REQUEST, "Bad Request (" + pe.getLocalizedMessage() + ')');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementation for reason header is also ready, it is based on RFC3326.
Should I add it here or in a separate bug report?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may introduce that later in a separate PR

} catch (Exception ex) {
logger.logError("error sending response", ex);
}
throw new TransactionUnavailableException(pe.getMessage(), pe);
}

if ( request.getMethod().equals(Request.ACK)) {
Expand Down
2 changes: 1 addition & 1 deletion src/gov/nist/javax/sip/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public abstract class Parser extends ParserCore implements TokenTypes {

protected ParseException createParseException(String exceptionString) {
return new ParseException(
lexer.getBuffer() + ":" + exceptionString,
lexer.getBuffer().trim() + ":" + exceptionString,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to "trim" here? messages should be already well formatted..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if parsing fails, like for example negative number value, buffer will contain new line (\n) at the end, so that's the reason for the trim

lexer.getPtr());
}

Expand Down
2 changes: 2 additions & 0 deletions src/gov/nist/javax/sip/stack/TCPMessageChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import gov.nist.javax.sip.header.CallID;
import gov.nist.javax.sip.header.ContentLength;
import gov.nist.javax.sip.header.From;
import gov.nist.javax.sip.header.MaxForwards;
import gov.nist.javax.sip.header.RequestLine;
import gov.nist.javax.sip.header.StatusLine;
import gov.nist.javax.sip.header.To;
Expand Down Expand Up @@ -445,6 +446,7 @@ public void handleException(ParseException ex, SIPMessage sipMessage,
|| hdrClass.equals(Via.class)
|| hdrClass.equals(CallID.class)
|| hdrClass.equals(ContentLength.class)
|| hdrClass.equals(MaxForwards.class)
|| hdrClass.equals(RequestLine.class) || hdrClass
.equals(StatusLine.class))) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
Expand Down
1 change: 1 addition & 0 deletions src/gov/nist/javax/sip/stack/TLSMessageChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ public void handleException(ParseException ex, SIPMessage sipMessage,
|| hdrClass.equals(Via.class)
|| hdrClass.equals(CallID.class)
|| hdrClass.equals(ContentLength.class)
|| hdrClass.equals(MaxForwards.class)
|| hdrClass.equals(RequestLine.class) || hdrClass
.equals(StatusLine.class))) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG))
Expand Down
2 changes: 2 additions & 0 deletions src/gov/nist/javax/sip/stack/UDPMessageChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import gov.nist.javax.sip.header.CallID;
import gov.nist.javax.sip.header.ContentLength;
import gov.nist.javax.sip.header.From;
import gov.nist.javax.sip.header.MaxForwards;
import gov.nist.javax.sip.header.RequestLine;
import gov.nist.javax.sip.header.StatusLine;
import gov.nist.javax.sip.header.To;
Expand Down Expand Up @@ -684,6 +685,7 @@ public void handleException(ParseException ex, SIPMessage sipMessage,
|| hdrClass.equals(Via.class)
|| hdrClass.equals(CallID.class)
|| hdrClass.equals(ContentLength.class)
|| hdrClass.equals(MaxForwards.class)
|| hdrClass.equals(RequestLine.class) || hdrClass
.equals(StatusLine.class))) {
if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,17 @@ public void testParser(){

}

// https://github.com/RestComm/jain-slee/issues/111
public void testNegativeContentLength(){
String error = "";
try {
final String header = "Content-Length: -123 \n";
System.out.print(header);
createParser(ContentLengthParser.class, header).parse();
} catch (java.text.ParseException ex) {
System.out.println(ex.getMessage());
error = ex.getLocalizedMessage();
}
assertTrue(error.contains("the contentLength parameter is <0"));
}
}
241 changes: 241 additions & 0 deletions src/test/unit/gov/nist/javax/sip/stack/RejectBadInviteMessageTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package test.unit.gov.nist.javax.sip.stack;

import java.text.ParseException;
import java.util.Properties;

import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.ListeningPoint;
import javax.sip.PeerUnavailableException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionUnavailableException;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.header.ContactHeader;
import javax.sip.header.HeaderFactory;
import javax.sip.header.ReasonHeader;
import javax.sip.header.ToHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;

import gov.nist.javax.sip.stack.NioMessageProcessorFactory;
import junit.framework.TestCase;

public class RejectBadInviteMessageTest extends TestCase {

public class Shootist implements SipListener {
private SipStack sipStack;

private SipProvider sipProvider;

private AddressFactory addressFactory;

private MessageFactory messageFactory;

private HeaderFactory headerFactory;

private ListeningPoint udpListeningPoint;

private static final String myAddress = "127.0.0.1";

private static final int myPort = 5070;

private boolean sawTransactionUnavailableException = false;

private boolean sawBadRequestResponse = false;

@Override
public void processRequest(RequestEvent requestEvent) {
Request request = requestEvent.getRequest();
ServerTransaction serverTransactionId = requestEvent.getServerTransaction();

System.out.println("Request " + request.getMethod() + " received at " + sipStack.getStackName()
+ " with server transaction id " + serverTransactionId);

if (request.getMethod().equals(Request.INVITE)) {
processInvite(requestEvent);
}
}

public void processInvite(RequestEvent requestEvent) {
SipProvider sipProvider = (SipProvider) requestEvent.getSource();
Request request = requestEvent.getRequest();
try {

Response okResponse = messageFactory.createResponse(Response.OK, request);
Address address = addressFactory.createAddress("Shootme <sip:" + myAddress + ":" + myPort + ">");
ContactHeader contactHeader = headerFactory.createContactHeader(address);
ToHeader toHeader = (ToHeader) okResponse.getHeader(ToHeader.NAME);
toHeader.setTag("4321"); // Application is supposed to set.
okResponse.addHeader(contactHeader);
ServerTransaction stx = requestEvent.getServerTransaction();
if (stx == null) {
stx = sipProvider.getNewServerTransaction(request);
stx.sendResponse(okResponse);
}
} catch (TransactionUnavailableException ex) {
this.sawTransactionUnavailableException = true;
System.out.println("Saw TransactionUnavailableException");
} catch (Exception ex) {
System.err.println("Unexpected exception" + ex);
fail("Unexpected Exception seen");
}
}

@Override
public void processResponse(ResponseEvent responseEvent) {
Response response = responseEvent.getResponse();
System.out.println("Response " + response.getStatusCode() + " received");

if (response.getStatusCode() == Response.BAD_REQUEST) {
this.sawBadRequestResponse = true;
System.out.println("ReasonPhrase is " + response.getReasonPhrase());
ReasonHeader reason = (ReasonHeader) response.getHeader(ReasonHeader.NAME);
if (reason != null) {
System.out.println("ReasonHeader cause=" + reason.getCause() + ", text=" + reason.getText());
} else {
System.out.println("ReasonHeader not available");
}
}
}

@Override
public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
System.out.println("dialogTerminatedEvent");
}

@Override
public void processTimeout(TimeoutEvent timeoutEvent) {
System.out.println("Got a timeout " + timeoutEvent.getClientTransaction());
}

@Override
public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
System.out.println("Transaction terminated event recieved");
}

@Override
public void processIOException(IOExceptionEvent exceptionEvent) {
System.out.println(
"IOException happened for " + exceptionEvent.getHost() + " port = " + exceptionEvent.getPort());
}

public void init() {
SipFactory sipFactory = null;
sipStack = null;
sipFactory = SipFactory.getInstance();
sipFactory.setPathName("gov.nist");
Properties properties = new Properties();
// If you want to try TCP transport change the following to
String transport = "udp";
String peerHostPort = myAddress + ':' + myPort;
properties.setProperty("javax.sip.OUTBOUND_PROXY", peerHostPort + "/" + transport);
// If you want to use UDP then uncomment this.
properties.setProperty("javax.sip.STACK_NAME", "shootist");

// The following properties are specific to nist-sip
// and are not necessarily part of any other jain-sip
// implementation.
// You can set a max message size for tcp transport to
// guard against denial of service attack.
properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "shootistdebug.txt");
properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "shootistlog.txt");

// Drop the client connection after we are done with the
// transaction.
properties.setProperty("gov.nist.javax.sip.CACHE_CLIENT_CONNECTIONS", "false");
// Set to 0 (or NONE) in your production code for max speed.
// You need 16 (or TRACE) for logging traces. 32 (or DEBUG) for
// debug + traces.
// Your code will limp at 32 but it is best for debugging.
properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "DEBUG");
if (System.getProperty("enableNIO") != null && System.getProperty("enableNIO").equalsIgnoreCase("true")) {
properties.setProperty("gov.nist.javax.sip.MESSAGE_PROCESSOR_FACTORY",
NioMessageProcessorFactory.class.getName());
}
try {
// Create SipStack object
sipStack = sipFactory.createSipStack(properties);
System.out.println("createSipStack " + sipStack);
} catch (PeerUnavailableException e) {
// could not find
// gov.nist.jain.protocol.ip.sip.SipStackImpl
// in the classpath
e.printStackTrace();
System.err.println(e.getMessage());
fail("Problem with setup");
}

try {
headerFactory = sipFactory.createHeaderFactory();
addressFactory = sipFactory.createAddressFactory();
messageFactory = sipFactory.createMessageFactory();
udpListeningPoint = sipStack.createListeningPoint(myAddress, myPort, "udp");
sipProvider = sipStack.createSipProvider(udpListeningPoint);
Shootist listener = this;
sipProvider.addSipListener(listener);

String badRequest = "INVITE sip:[email protected]:5070 SIP/2.0\r\n"
+ "Call-ID: [email protected]\r\n"
+ "CSeq: 1 INVITE\r\n"
+ "From: \"The Master Blaster\" <sip:[email protected]>;tag=12345\r\n"
+ "To: \"The Little Blister\" <sip:[email protected]>\r\n"
+ "Via: SIP/2.0/UDP 127.0.0.1:5070 ;branch=z9hG4bKba9abfef1063941840d955a5e3e7dae0393734\r\n"
+ "Max-Forwards: 70\r\n"
+ "Content-Length: 0\r\n\r\n";

System.out.println("Parsing message \n" + badRequest);
Request request = null;
try {
request = messageFactory.createRequest(badRequest);
} catch (ParseException ex) {
System.out.println("Unexpected exception " + ex);
fail("Unexpected exception");
}

// send the request out.
sipProvider.sendRequest(request);

} catch (Exception ex) {
ex.printStackTrace();
fail("cannot create or send initial invite");
}
}

public void terminate() {
this.sipStack.stop();
}
}

private Shootist shootist;

@Override
public void setUp() {
this.shootist = new Shootist();
}

@Override
public void tearDown() {
shootist.terminate();
}

public void testSendInviteWithoutContactHeader() {
this.shootist.init();
try {
Thread.sleep(500);
} catch (Exception ex) {
}

assertTrue("Should see TransactionUnavailableException", shootist.sawTransactionUnavailableException);
assertTrue("Should see 400 Bad Request response", shootist.sawBadRequestResponse);
}
}