Merge branch 'jetty-9.4.x' into jetty-9.4.x-ewyk

This commit is contained in:
Greg Wilkins 2017-03-30 14:13:36 +11:00
commit bacf51a83d
8 changed files with 348 additions and 512 deletions

View File

@ -45,6 +45,11 @@ public class BadMessageException extends RuntimeException
this(400,reason); this(400,reason);
} }
public BadMessageException(String reason, Throwable cause)
{
this(400, reason, cause);
}
public BadMessageException(int code, String reason) public BadMessageException(int code, String reason)
{ {
super(code+": "+reason); super(code+": "+reason);

View File

@ -21,16 +21,15 @@ package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.Utf8Appendable;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
public class HttpURITest public class HttpURITest
@ -100,33 +99,6 @@ public class HttpURITest
assertThat(uri.getHost(),is("foo")); assertThat(uri.getHost(),is("foo"));
assertThat(uri.getPath(),is("/bar")); assertThat(uri.getPath(),is("/bar"));
} }
@Test
public void testUnicodeErrors() throws UnsupportedEncodingException
{
String uri="http://server/path?invalid=data%uXXXXhere%u000";
try
{
URLDecoder.decode(uri,"UTF-8");
Assert.assertTrue(false);
}
catch (IllegalArgumentException e)
{
}
HttpURI huri=new HttpURI(uri);
MultiMap<String> params = new MultiMap<>();
huri.decodeQueryTo(params);
assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
huri=new HttpURI(uri);
params = new MultiMap<>();
huri.decodeQueryTo(params,StandardCharsets.UTF_8);
assertEquals("data"+Utf8Appendable.REPLACEMENT+"here"+Utf8Appendable.REPLACEMENT,params.getValue("invalid",0));
}
@Test @Test
public void testExtB() throws Exception public void testExtB() throws Exception

View File

@ -363,13 +363,31 @@ public class Request implements HttpServletRequest
// once extracted and may have already been extracted by getParts() or // once extracted and may have already been extracted by getParts() or
// by a processing happening after a form-based authentication. // by a processing happening after a form-based authentication.
if (_contentParameters == null) if (_contentParameters == null)
extractContentParameters(); {
try
{
extractContentParameters();
}
catch(IllegalStateException | IllegalArgumentException e)
{
throw new BadMessageException("Unable to parse form content", e);
}
}
} }
// Extract query string parameters; these may be replaced by a forward() // Extract query string parameters; these may be replaced by a forward()
// and may have already been extracted by mergeQueryParameters(). // and may have already been extracted by mergeQueryParameters().
if (_queryParameters == null) if (_queryParameters == null)
extractQueryParameters(); {
try
{
extractQueryParameters();
}
catch(IllegalStateException | IllegalArgumentException e)
{
throw new BadMessageException("Unable to parse URI query", e);
}
}
// Do parameters need to be combined? // Do parameters need to be combined?
if (_queryParameters==NO_PARAMS || _queryParameters.size()==0) if (_queryParameters==NO_PARAMS || _queryParameters.size()==0)

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -57,16 +58,17 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part; import javax.servlet.http.Part;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint; import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStreamParser; import org.eclipse.jetty.util.MultiPartInputStreamParser;
import org.eclipse.jetty.util.Utf8Appendable;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.log.StacklessLogging;
@ -97,6 +99,10 @@ public class RequestTest
_server.addConnector(_connector); _server.addConnector(_connector);
_handler = new RequestHandler(); _handler = new RequestHandler();
_server.setHandler(_handler); _server.setHandler(_handler);
ErrorHandler errors = new ErrorHandler();
errors.setShowStacks(true);
_server.addBean(errors);
_server.start(); _server.start();
} }
@ -115,13 +121,19 @@ public class RequestTest
@Override @Override
public boolean check(HttpServletRequest request,HttpServletResponse response) public boolean check(HttpServletRequest request,HttpServletResponse response)
{ {
Map<String,String[]> map = null; try
//do the parse {
map = request.getParameterMap(); Map<String, String[]> map = null;
assertEquals("aaa"+Utf8Appendable.REPLACEMENT+"bbb",map.get("param")[0]); // do the parse
assertEquals("value",map.get("other")[0]); map = request.getParameterMap();
return false;
return true; }
catch(BadMessageException e)
{
// Should be able to retrieve the raw query
String rawQuery = request.getQueryString();
return rawQuery.equals("param=aaa%ZZbbb&other=value");
}
} }
}; };
@ -135,7 +147,6 @@ public class RequestTest
String responses=_connector.getResponse(request); String responses=_connector.getResponse(request);
assertTrue(responses.startsWith("HTTP/1.1 200")); assertTrue(responses.startsWith("HTTP/1.1 200"));
} }
@Test @Test
@ -265,7 +276,7 @@ public class RequestTest
"Accept-Language: XX;q=0, en-au;q=0.9\r\n"+ "Accept-Language: XX;q=0, en-au;q=0.9\r\n"+
"\r\n"; "\r\n";
String response = _connector.getResponse(request); String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK")); assertThat(response, containsString(" 200 OK"));
} }
@ -402,8 +413,18 @@ public class RequestTest
@Override @Override
public boolean check(HttpServletRequest request,HttpServletResponse response) public boolean check(HttpServletRequest request,HttpServletResponse response)
{ {
String value=request.getParameter("param"); try
return value.startsWith("aaa") && value.endsWith("bb"); {
// This throws an exception if attempted
request.getParameter("param");
return false;
}
catch(BadMessageException e)
{
// Should still be able to get the raw query.
String rawQuery = request.getQueryString();
return rawQuery.equals("param=aaa%E7bbb");
}
} }
}; };
@ -523,7 +544,7 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
int i=0; int i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://myhost/",results.get(i++)); assertEquals("http://myhost/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++)); assertEquals("myhost",results.get(i++));
@ -537,7 +558,7 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++)); assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++)); assertEquals("myhost",results.get(i++));
@ -549,7 +570,7 @@ public class RequestTest
"GET http://myhost:8888/ HTTP/1.0\n"+ "GET http://myhost:8888/ HTTP/1.0\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++)); assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++)); assertEquals("myhost",results.get(i++));
@ -562,7 +583,7 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://myhost:8888/",results.get(i++)); assertEquals("http://myhost:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("myhost",results.get(i++)); assertEquals("myhost",results.get(i++));
@ -577,7 +598,7 @@ public class RequestTest
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://1.2.3.4/",results.get(i++)); assertEquals("http://1.2.3.4/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++)); assertEquals("1.2.3.4",results.get(i++));
@ -591,7 +612,7 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://1.2.3.4:8888/",results.get(i++)); assertEquals("http://1.2.3.4:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("1.2.3.4",results.get(i++)); assertEquals("1.2.3.4",results.get(i++));
@ -605,7 +626,7 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://[::1]/",results.get(i++)); assertEquals("http://[::1]/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++)); assertEquals("[::1]",results.get(i++));
@ -619,7 +640,7 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("http://[::1]:8888/",results.get(i++)); assertEquals("http://[::1]:8888/",results.get(i++));
assertEquals("0.0.0.0",results.get(i++)); assertEquals("0.0.0.0",results.get(i++));
assertEquals("[::1]",results.get(i++)); assertEquals("[::1]",results.get(i++));
@ -635,7 +656,7 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("https://[::1]/",results.get(i++)); assertEquals("https://[::1]/",results.get(i++));
assertEquals("remote",results.get(i++)); assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++)); assertEquals("[::1]",results.get(i++));
@ -651,7 +672,7 @@ public class RequestTest
"x-forwarded-proto: https\n"+ "x-forwarded-proto: https\n"+
"\n"); "\n");
i=0; i=0;
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertEquals("https://[::1]:8888/",results.get(i++)); assertEquals("https://[::1]:8888/",results.get(i++));
assertEquals("remote",results.get(i++)); assertEquals("remote",results.get(i++));
assertEquals("[::1]",results.get(i++)); assertEquals("[::1]",results.get(i++));
@ -699,7 +720,7 @@ public class RequestTest
Log.getRootLogger().debug("test l={}",l); Log.getRootLogger().debug("test l={}",l);
String response = _connector.getResponse(request); String response = _connector.getResponse(request);
Log.getRootLogger().debug(response); Log.getRootLogger().debug(response);
assertThat(response, Matchers.containsString(" 200 OK")); assertThat(response, containsString(" 200 OK"));
assertEquals(l,length.get()); assertEquals(l,length.get());
content+="x"; content+="x";
} }
@ -728,7 +749,7 @@ public class RequestTest
"\r\n"+ "\r\n"+
content; content;
String response = _connector.getResponse(request); String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK")); assertThat(response, containsString(" 200 OK"));
} }
@Test @Test
@ -752,7 +773,7 @@ public class RequestTest
"\r\n"+ "\r\n"+
content; content;
String response = _connector.getResponse(request); String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK")); assertThat(response, containsString(" 200 OK"));
} }
@Test @Test
@ -778,7 +799,7 @@ public class RequestTest
"\r\n"+ "\r\n"+
content; content;
String response = _connector.getResponse(request); String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK")); assertThat(response, containsString(" 200 OK"));
} }
@Test @Test
@ -806,7 +827,7 @@ public class RequestTest
"\r\n"+ "\r\n"+
content; content;
String response = _connector.getResponse(request); String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK")); assertThat(response, containsString(" 200 OK"));
} }
@Test @Test
@ -834,7 +855,7 @@ public class RequestTest
"\r\n"+ "\r\n"+
content; content;
String response = _connector.getResponse(request); String response = _connector.getResponse(request);
assertThat(response,Matchers.containsString(" 200 OK")); assertThat(response, containsString(" 200 OK"));
} }
@ -1026,8 +1047,8 @@ public class RequestTest
"Host: myhost\n"+ "Host: myhost\n"+
"Connection: close\n"+ "Connection: close\n"+
"\n"); "\n");
assertThat(response,Matchers.containsString(" 302 Found")); assertThat(response, containsString(" 302 Found"));
assertThat(response,Matchers.containsString("Location: http://myhost/foo")); assertThat(response, containsString("Location: http://myhost/foo"));
} }
@Test @Test
@ -1097,9 +1118,9 @@ public class RequestTest
"\n", "\n",
200, TimeUnit.MILLISECONDS 200, TimeUnit.MILLISECONDS
); );
assertThat(response, Matchers.containsString("200")); assertThat(response, containsString("200"));
assertThat(response, Matchers.not(Matchers.containsString("Connection: close"))); assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, Matchers.containsString("Hello World")); assertThat(response, containsString("Hello World"));
response=_connector.getResponse( response=_connector.getResponse(
"GET / HTTP/1.1\n"+ "GET / HTTP/1.1\n"+
@ -1107,9 +1128,9 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n" "\n"
); );
assertThat(response, Matchers.containsString("200")); assertThat(response, containsString("200"));
assertThat(response, Matchers.containsString("Connection: close")); assertThat(response, containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World")); assertThat(response, containsString("Hello World"));
response=_connector.getResponse( response=_connector.getResponse(
"GET / HTTP/1.1\n"+ "GET / HTTP/1.1\n"+
@ -1118,18 +1139,18 @@ public class RequestTest
"\n" "\n"
); );
assertThat(response, Matchers.containsString("200")); assertThat(response, containsString("200"));
assertThat(response, Matchers.containsString("Connection: close")); assertThat(response, containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World")); assertThat(response, containsString("Hello World"));
response=_connector.getResponse( response=_connector.getResponse(
"GET / HTTP/1.0\n"+ "GET / HTTP/1.0\n"+
"Host: whatever\n"+ "Host: whatever\n"+
"\n" "\n"
); );
assertThat(response, Matchers.containsString("200")); assertThat(response, containsString("200"));
assertThat(response, Matchers.not(Matchers.containsString("Connection: close"))); assertThat(response, Matchers.not(containsString("Connection: close")));
assertThat(response, Matchers.containsString("Hello World")); assertThat(response, containsString("Hello World"));
response=_connector.getResponse( response=_connector.getResponse(
"GET / HTTP/1.0\n"+ "GET / HTTP/1.0\n"+
@ -1137,8 +1158,8 @@ public class RequestTest
"Connection: Other, close\n"+ "Connection: Other, close\n"+
"\n" "\n"
); );
assertThat(response, Matchers.containsString("200")); assertThat(response, containsString("200"));
assertThat(response, Matchers.containsString("Hello World")); assertThat(response, containsString("Hello World"));
response=_connector.getResponse( response=_connector.getResponse(
"GET / HTTP/1.0\n"+ "GET / HTTP/1.0\n"+
@ -1147,9 +1168,9 @@ public class RequestTest
"\n", "\n",
200, TimeUnit.MILLISECONDS 200, TimeUnit.MILLISECONDS
); );
assertThat(response, Matchers.containsString("200")); assertThat(response, containsString("200"));
assertThat(response, Matchers.containsString("Connection: keep-alive")); assertThat(response, containsString("Connection: keep-alive"));
assertThat(response, Matchers.containsString("Hello World")); assertThat(response, containsString("Hello World"));
_handler._checker = new RequestTester() _handler._checker = new RequestTester()
{ {
@ -1169,10 +1190,9 @@ public class RequestTest
"\n", "\n",
200, TimeUnit.MILLISECONDS 200, TimeUnit.MILLISECONDS
); );
assertThat(response, Matchers.containsString("200")); assertThat(response, containsString("200"));
assertThat(response, Matchers.containsString("Connection: TE")); assertThat(response, containsString("Connection: TE"));
assertThat(response, Matchers.containsString("Connection: Other")); assertThat(response, containsString("Connection: Other"));
assertThat(response, Matchers.containsString("Hello World"));
response=_connector.getResponse( response=_connector.getResponse(
"GET / HTTP/1.1\n"+ "GET / HTTP/1.1\n"+
@ -1180,9 +1200,9 @@ public class RequestTest
"Connection: close\n"+ "Connection: close\n"+
"\n" "\n"
); );
assertThat(response, Matchers.containsString("200 OK")); assertThat(response, containsString("200 OK"));
assertThat(response, Matchers.containsString("Connection: close")); assertThat(response, containsString("Connection: close"));
assertThat(response, Matchers.containsString("Hello World")); assertThat(response, containsString("Hello World"));
} }
@Test @Test
@ -1427,11 +1447,10 @@ public class RequestTest
{ {
try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class)) try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class))
{ {
LOG.info("Expecting maxFormKeys limit and Closing HttpParser exceptions..."); // Expecting maxFormKeys limit and Closing HttpParser exceptions...
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1); _server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1);
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000); _server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
StringBuilder buf = new StringBuilder(4000000); StringBuilder buf = new StringBuilder(4000000);
buf.append("a=b"); buf.append("a=b");
@ -1439,7 +1458,7 @@ public class RequestTest
File evil_keys = new File("/tmp/keys_mapping_to_zero_2m"); File evil_keys = new File("/tmp/keys_mapping_to_zero_2m");
if (evil_keys.exists()) if (evil_keys.exists())
{ {
LOG.info("Using real evil keys!"); // Using real evil keys!
try (BufferedReader in = new BufferedReader(new FileReader(evil_keys))) try (BufferedReader in = new BufferedReader(new FileReader(evil_keys)))
{ {
String key=null; String key=null;
@ -1474,8 +1493,11 @@ public class RequestTest
buf; buf;
long start=System.currentTimeMillis(); long start=System.currentTimeMillis();
String response = _connector.getResponse(request); String rawResponse = _connector.getResponse(request);
assertThat(response,Matchers.containsString("IllegalStateException")); HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response.status", response.getStatus(), is(400));
assertThat("Response body content", response.getContent(),containsString(BadMessageException.class.getName()));
assertThat("Response body content", response.getContent(),containsString(IllegalStateException.class.getName()));
long now=System.currentTimeMillis(); long now=System.currentTimeMillis();
assertTrue((now-start)<5000); assertTrue((now-start)<5000);
} }
@ -1515,8 +1537,11 @@ public class RequestTest
buf; buf;
long start=System.currentTimeMillis(); long start=System.currentTimeMillis();
String response = _connector.getResponse(request); String rawResponse = _connector.getResponse(request);
assertTrue(response.contains("IllegalStateException")); HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat("Response.status", response.getStatus(), is(400));
assertThat("Response body content", response.getContent(),containsString(BadMessageException.class.getName()));
assertThat("Response body content", response.getContent(),containsString(IllegalStateException.class.getName()));
long now=System.currentTimeMillis(); long now=System.currentTimeMillis();
assertTrue((now-start)<5000); assertTrue((now-start)<5000);
} }

View File

@ -29,7 +29,6 @@ import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -316,88 +315,53 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
for (int i=offset;i<end;i++) for (int i=offset;i<end;i++)
{ {
char c=query.charAt(i); char c=query.charAt(i);
try switch (c)
{ {
switch (c) case '&':
{ value = buffer.toReplacedString();
case '&': buffer.reset();
value = buffer.toReplacedString(); if (key != null)
buffer.reset(); {
if (key != null) map.add(key,value);
{ }
map.add(key,value); else if (value!=null&&value.length()>0)
} {
else if (value!=null&&value.length()>0) map.add(value,"");
{ }
map.add(value,""); key = null;
} value=null;
key = null; break;
value=null;
break;
case '=': case '=':
if (key!=null) if (key!=null)
{ {
buffer.append(c);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
if (i+2<end)
{
if ('u'==query.charAt(i+1))
{
i++;
if (i+4<end)
{
char top=query.charAt(++i);
char hi=query.charAt(++i);
char lo=query.charAt(++i);
char bot=query.charAt(++i);
buffer.getStringBuilder().append(Character.toChars((convertHexDigit(top)<<12) +(convertHexDigit(hi)<<8) + (convertHexDigit(lo)<<4) +convertHexDigit(bot)));
}
else
{
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
i=end;
}
}
else
{
char hi=query.charAt(++i);
char lo=query.charAt(++i);
buffer.append((byte)((convertHexDigit(hi)<<4) + convertHexDigit(lo)));
}
}
else
{
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
i=end;
}
break;
default:
buffer.append(c); buffer.append(c);
break; break;
} }
} key = buffer.toReplacedString();
catch(NotUtf8Exception e) buffer.reset();
{ break;
LOG.warn(e.toString());
LOG.debug(e); case '+':
} buffer.append((byte)' ');
catch(NumberFormatException e) break;
{
buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3); case '%':
LOG.warn(e.toString()); if (i+2<end)
LOG.debug(e); {
char hi=query.charAt(++i);
char lo=query.charAt(++i);
buffer.append(decodeHexByte(hi,lo));
}
else
{
throw new Utf8Appendable.NotUtf8Exception("Incomplete % encoding");
}
break;
default:
buffer.append(c);
break;
} }
} }
@ -472,26 +436,8 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
case '%': case '%':
int code0=in.read(); int code0=in.read();
if ('u'==code0) int code1=in.read();
{ buffer.append(decodeHexChar(code0,code1));
int code1=in.read();
if (code1>=0)
{
int code2=in.read();
if (code2>=0)
{
int code3=in.read();
if (code3>=0)
buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
}
}
}
else if (code0>=0)
{
int code1=in.read();
if (code1>=0)
buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
}
break; break;
default: default:
@ -537,99 +483,51 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
int totalLength=0; int totalLength=0;
while ((b=in.read())>=0) while ((b=in.read())>=0)
{ {
try switch ((char) b)
{ {
switch ((char) b) case '&':
{ value = buffer.toReplacedString();
case '&': buffer.reset();
value = buffer.toReplacedString(); if (key != null)
buffer.reset(); {
if (key != null) map.add(key,value);
{ }
map.add(key,value); else if (value!=null&&value.length()>0)
} {
else if (value!=null&&value.length()>0) map.add(value,"");
{ }
map.add(value,""); key = null;
} value=null;
key = null; if (maxKeys>0 && map.size()>maxKeys)
value=null;
if (maxKeys>0 && map.size()>maxKeys)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys)); throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys));
break; break;
case '=': case '=':
if (key!=null) if (key!=null)
{ {
buffer.append((byte)b);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
int code0=in.read();
boolean decoded=false;
if ('u'==code0)
{
code0=in.read(); // XXX: we have to read the next byte, otherwise code0 is always 'u'
if (code0>=0)
{
int code1=in.read();
if (code1>=0)
{
int code2=in.read();
if (code2>=0)
{
int code3=in.read();
if (code3>=0)
{
buffer.getStringBuilder().append(Character.toChars
((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
decoded=true;
}
}
}
}
}
else if (code0>=0)
{
int code1=in.read();
if (code1>=0)
{
buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
decoded=true;
}
}
if (!decoded)
buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
break;
default:
buffer.append((byte)b); buffer.append((byte)b);
break; break;
} }
} key = buffer.toReplacedString();
catch(NotUtf8Exception e) buffer.reset();
{ break;
LOG.warn(e.toString());
LOG.debug(e); case '+':
} buffer.append((byte)' ');
catch(NumberFormatException e) break;
{
buffer.append(Utf8Appendable.REPLACEMENT_UTF8,0,3); case '%':
LOG.warn(e.toString()); char code0= (char) in.read();
LOG.debug(e); char code1= (char) in.read();
buffer.append(decodeHexByte(code0,code1));
break;
default:
buffer.append((byte)b);
break;
} }
if (maxLength>=0 && (++totalLength > maxLength)) if (maxLength>=0 && (++totalLength > maxLength))
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys)); throw new IllegalStateException("Form is too large");
} }
if (key != null) if (key != null)
@ -768,27 +666,8 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
break; break;
case '%': case '%':
int code0=in.read(); int code0=in.read();
if ('u'==code0) int code1=in.read();
{ output.write(decodeHexChar(code0,code1));
int code1=in.read();
if (code1>=0)
{
int code2=in.read();
if (code2>=0)
{
int code3=in.read();
if (code3>=0)
output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
}
}
}
else if (code0>=0)
{
int code1=in.read();
if (code1>=0)
output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
}
break; break;
default: default:
output.write(c); output.write(c);
@ -797,7 +676,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
totalLength++; totalLength++;
if (maxLength>=0 && totalLength > maxLength) if (maxLength>=0 && totalLength > maxLength)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]",map.size(),maxKeys)); throw new IllegalStateException("Form is too large");
} }
size=output.size(); size=output.size();
@ -874,42 +753,10 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
if ((i+2)<length) if ((i+2)<length)
{ {
try int o=offset+i+1;
{ i+=2;
if ('u'==encoded.charAt(offset+i+1)) byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
{ buffer.append(b);
if((i+5)<length)
{
int o=offset+i+2;
i+=5;
String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
buffer.getStringBuffer().append(unicode);
}
else
{
i=length;
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
}
}
else
{
int o=offset+i+1;
i+=2;
byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
buffer.append(b);
}
}
catch(NotUtf8Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
}
catch(NumberFormatException e)
{
LOG.warn(e.toString());
LOG.debug(e);
buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);
}
} }
else else
{ {
@ -973,44 +820,15 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
{ {
if(i+2<length) if(i+2<length)
{ {
try int o=offset+i+1;
{ i+=3;
if ('u'==encoded.charAt(offset+i+1)) ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
{ n++;
if (i+6<length)
{
int o=offset+i+2;
i+=6;
String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
byte[] reencoded = unicode.getBytes(charset);
System.arraycopy(reencoded,0,ba,n,reencoded.length);
n+=reencoded.length;
}
else
{
ba[n++] = (byte)'?';
i=length;
}
}
else
{
int o=offset+i+1;
i+=3;
ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
n++;
}
}
catch(Exception e)
{
LOG.warn(e.toString());
LOG.debug(e);
ba[n++] = (byte)'?';
}
} }
else else
{ {
ba[n++] = (byte)'?'; ba[n++] = (byte)'?';
i=length; i=length;
} }
} }
else if (c=='+') else if (c=='+')
@ -1046,7 +864,30 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
return buffer.toString(); return buffer.toString();
} }
}
private static char decodeHexChar(int hi, int lo)
{
try
{
return (char) ((convertHexDigit(hi) << 4) + convertHexDigit(lo));
}
catch(NumberFormatException e)
{
throw new IllegalArgumentException("Not valid encoding '%" + (char) hi + (char) lo + "'");
}
}
private static byte decodeHexByte(char hi, char lo)
{
try
{
return (byte) ((convertHexDigit(hi) << 4) + convertHexDigit(lo));
}
catch(NumberFormatException e)
{
throw new IllegalArgumentException("Not valid encoding '%" + hi + lo + "'");
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -18,7 +18,9 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -26,17 +28,16 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
/* ------------------------------------------------------------ */ /**
/** Util meta Tests. * URL Encoding / Decoding Tests
*
*/ */
public class URLEncodedTest public class URLEncodedTest
{ {
/* -------------------------------------------------------------- */
static static
{ {
/* /*
@ -46,9 +47,10 @@ public class URLEncodedTest
System.setProperty("org.eclipse.jetty.util.UrlEncoding.charset", StringUtil.__ISO_8859_1); System.setProperty("org.eclipse.jetty.util.UrlEncoding.charset", StringUtil.__ISO_8859_1);
*/ */
} }
@Rule
public ExpectedException expectedException = ExpectedException.none();
/* -------------------------------------------------------------- */
@Test @Test
public void testUrlEncoded() public void testUrlEncoded()
{ {
@ -120,82 +122,8 @@ public class URLEncodedTest
assertEquals("encoded param size",1, url_encoded.size()); assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded encode","Name8=xx%2C++yy++%2Czz", url_encoded.encode()); assertEquals("encoded encode","Name8=xx%2C++yy++%2Czz", url_encoded.encode());
assertEquals("encoded get", url_encoded.getString("Name8"),"xx, yy ,zz"); assertEquals("encoded get", url_encoded.getString("Name8"),"xx, yy ,zz");
url_encoded.clear();
url_encoded.decode("Name11=%u30EDxxVerdi+%C6+og+2zz", StandardCharsets.ISO_8859_1);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "?xxVerdi \u00c6 og 2zz",url_encoded.getString("Name11"));
url_encoded.clear();
url_encoded.decode("Name12=%u30EDxxVerdi+%2F+og+2zz", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", url_encoded.getString("Name12"),"\u30edxxVerdi / og 2zz");
url_encoded.clear();
url_encoded.decode("Name14=%uXXXXa%GGb%+%c%+%d", StandardCharsets.ISO_8859_1);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get","?a?b?c?d", url_encoded.getString("Name14"));
url_encoded.clear();
url_encoded.decode("Name14=%uXXXX%GG%+%%+%", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd\ufffd",url_encoded.getString("Name14"));
/* Not every jvm supports this encoding */
if (java.nio.charset.Charset.isSupported("SJIS"))
{
url_encoded.clear();
url_encoded.decode("Name9=%u30ED%83e%83X%83g", Charset.forName("SJIS")); // "Test" in Japanese Katakana
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\u30ed\u30c6\u30b9\u30c8", url_encoded.getString("Name9"));
}
else
assertTrue("Charset SJIS not supported by jvm", true);
} }
/* -------------------------------------------------------------- */
@Test
public void testBadEncoding()
{
UrlEncoded url_encoded = new UrlEncoded();
url_encoded.decode("Name15=xx%zzyy", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "xx\ufffdyy", url_encoded.getString("Name15"));
String bad="Name=%FF%FF%FF";
MultiMap<String> map = new MultiMap<String>();
UrlEncoded.decodeUtf8To(bad,map);
assertEquals("encoded param size",1, map.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd", map.getString("Name"));
url_encoded.clear();
url_encoded.decode("Name=%FF%FF%FF", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd\ufffd", url_encoded.getString("Name"));
url_encoded.clear();
url_encoded.decode("Name=%EF%EF%EF", StandardCharsets.UTF_8);
assertEquals("encoded param size",1, url_encoded.size());
assertEquals("encoded get", "\ufffd\ufffd", url_encoded.getString("Name"));
assertEquals("x",UrlEncoded.decodeString("x",0,1,StandardCharsets.UTF_8));
assertEquals("x\ufffd",UrlEncoded.decodeString("x%",0,2,StandardCharsets.UTF_8));
assertEquals("x\ufffd",UrlEncoded.decodeString("x%2",0,3,StandardCharsets.UTF_8));
assertEquals("x ",UrlEncoded.decodeString("x%20",0,4,StandardCharsets.UTF_8));
assertEquals("xxx",UrlEncoded.decodeString("xxx",0,3,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%",0,4,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u",0,5,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u1",0,6,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u12",0,7,StandardCharsets.UTF_8));
assertEquals("xxx\ufffd",UrlEncoded.decodeString("xxx%u123",0,8,StandardCharsets.UTF_8));
assertEquals("xxx\u1234",UrlEncoded.decodeString("xxx%u1234",0,9,StandardCharsets.UTF_8));
}
/* -------------------------------------------------------------- */ /* -------------------------------------------------------------- */
@Test @Test
public void testUrlEncodedStream() public void testUrlEncodedStream()
@ -209,6 +137,7 @@ public class URLEncodedTest
{StringUtil.__UTF16,StringUtil.__UTF16,"%00%30"}, {StringUtil.__UTF16,StringUtil.__UTF16,"%00%30"},
}; };
// Note: "%30" -> decode -> "0"
for (int i=0;i<charsets.length;i++) for (int i=0;i<charsets.length;i++)
{ {
@ -216,10 +145,10 @@ public class URLEncodedTest
MultiMap<String> m = new MultiMap<>(); MultiMap<String> m = new MultiMap<>();
UrlEncoded.decodeTo(in, m, charsets[i][1]==null?null:Charset.forName(charsets[i][1]),-1,-1); UrlEncoded.decodeTo(in, m, charsets[i][1]==null?null:Charset.forName(charsets[i][1]),-1,-1);
assertEquals(charsets[i][1]+" stream length",4,m.size()); assertEquals(charsets[i][1]+" stream length",4,m.size());
assertEquals(charsets[i][1]+" stream name\\n","value 0",m.getString("name\n")); assertThat(charsets[i][1]+" stream name\\n",m.getString("name\n"),is("value 0"));
assertEquals(charsets[i][1]+" stream name1","",m.getString("name1")); assertThat(charsets[i][1]+" stream name1",m.getString("name1"),is(""));
assertEquals(charsets[i][1]+" stream name2","",m.getString("name2")); assertThat(charsets[i][1]+" stream name2",m.getString("name2"),is(""));
assertEquals(charsets[i][1]+" stream n\u00e3me3","value 3",m.getString("n\u00e3me3")); assertThat(charsets[i][1]+" stream n\u00e3me3",m.getString("n\u00e3me3"),is("value 3"));
} }
@ -270,21 +199,19 @@ public class URLEncodedTest
String expected = new String(TypeUtil.fromHexString(hex),"utf-8"); String expected = new String(TypeUtil.fromHexString(hex),"utf-8");
Assert.assertEquals(expected,url_encoded.getString("text")); Assert.assertEquals(expected,url_encoded.getString("text"));
} }
/* -------------------------------------------------------------- */
@Test @Test
public void testNotUtf8() throws Exception public void testUtf8_MultiByteCodePoint()
{ {
String query="name=X%c0%afZ"; String input = "text=test%C3%A4";
UrlEncoded url_encoded = new UrlEncoded();
MultiMap<String> map = new MultiMap<>(); url_encoded.decode(input);
UrlEncoded.LOG.info("EXPECT 4 Not Valid UTF8 warnings...");
UrlEncoded.decodeUtf8To(query,0,query.length(),map); // http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00e4&mode=hex
assertEquals("X"+Utf8Appendable.REPLACEMENT+Utf8Appendable.REPLACEMENT+"Z",map.getValue("name",0)); // Should be "testä"
// "test" followed by a LATIN SMALL LETTER A WITH DIAERESIS
map.clear();
String expected = "test\u00e4";
UrlEncoded.decodeUtf8To(new ByteArrayInputStream(query.getBytes(StandardCharsets.ISO_8859_1)),map,100,-1); assertThat(url_encoded.getString("text"),is(expected));
assertEquals("X"+Utf8Appendable.REPLACEMENT+Utf8Appendable.REPLACEMENT+"Z",map.getValue("name",0));
} }
} }

View File

@ -0,0 +1,87 @@
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class UrlEncodedInvalidEncodingTest
{
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Parameterized.Parameters(name = "{1} | {0}")
public static List<Object[]> data()
{
ArrayList<Object[]> data = new ArrayList<>();
data.add(new Object[]{ "Name=xx%zzyy", UTF_8, IllegalArgumentException.class });
data.add(new Object[]{ "Name=%FF%FF%FF", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=%EF%EF%EF", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=%E%F%F", UTF_8, IllegalArgumentException.class });
data.add(new Object[]{ "Name=x%", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=x%2", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "Name=xxx%", UTF_8, Utf8Appendable.NotUtf8Exception.class });
data.add(new Object[]{ "name=X%c0%afZ", UTF_8, Utf8Appendable.NotUtf8Exception.class });
return data;
}
@Parameterized.Parameter(0)
public String inputString;
@Parameterized.Parameter(1)
public Charset charset;
@Parameterized.Parameter(2)
public Class<? extends Throwable> expectedThrowable;
@Test
public void testDecode()
{
UrlEncoded url_encoded = new UrlEncoded();
expectedException.expect(expectedThrowable);
url_encoded.decode(inputString, charset);
}
@Test
public void testDecodeUtf8ToMap()
{
MultiMap<String> map = new MultiMap<String>();
expectedException.expect(expectedThrowable);
UrlEncoded.decodeUtf8To(inputString,map);
}
@Test
public void testDecodeTo()
{
MultiMap<String> map = new MultiMap<String>();
expectedException.expect(expectedThrowable);
UrlEncoded.decodeTo(inputString,map,charset);
}
}

View File

@ -80,45 +80,6 @@ public class UrlEncodedUtf8Test
fromInputStream(test,bytes,name,value,false); fromInputStream(test,bytes,name,value,false);
} }
@Test
public void testCorrectUnicode() throws Exception
{
String chars="a=%u0061";
byte[] bytes= chars.getBytes(StandardCharsets.UTF_8);
String test=new String(bytes,StandardCharsets.UTF_8);
String name = "a";
String value = "a";
fromString(test,test,name,value,false);
fromInputStream(test,bytes,name,value,false);
}
@Test
public void testIncompleteUnicode() throws Exception
{
String chars="a=%u0";
byte[] bytes= chars.getBytes(StandardCharsets.UTF_8);
String test=new String(bytes,StandardCharsets.UTF_8);
String name = "a";
String value = ""+Utf8Appendable.REPLACEMENT;
fromString(test,test,name,value,false);
fromInputStream(test,bytes,name,value,false);
}
@Test
public void testIncompletePercent() throws Exception
{
String chars="a=%A";
byte[] bytes= chars.getBytes(StandardCharsets.UTF_8);
String test=new String(bytes,StandardCharsets.UTF_8);
String name = "a";
String value = ""+Utf8Appendable.REPLACEMENT;
fromString(test,test,name,value,false);
fromInputStream(test,bytes,name,value,false);
}
static void fromString(String test,String s,String field,String expected,boolean thrown) throws Exception static void fromString(String test,String s,String field,String expected,boolean thrown) throws Exception
{ {
MultiMap<String> values=new MultiMap<>(); MultiMap<String> values=new MultiMap<>();