// ======================================================================== // Copyright (c) 2004-2009 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.server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import junit.framework.Assert; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * */ public class RequestTest { private Server _server; private LocalConnector _connector; private RequestHandler _handler; @Before public void init() throws Exception { _server = new Server(); _connector = new LocalConnector(); _connector.setRequestHeaderSize(512); _connector.setRequestBufferSize(1024); _connector.setResponseHeaderSize(512); _connector.setResponseBufferSize(2048); _connector.setForwarded(true); _server.addConnector(_connector); _handler = new RequestHandler(); _server.setHandler(_handler); _server.start(); } @After public void destroy() throws Exception { _server.stop(); _server.join(); } @Test public void testParamExtraction() throws Exception { _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) { Map map = null; try { //do the parse request.getParameterMap(); Assert.fail("Expected parsing failure"); return false; } catch (Exception e) { //catch the error and check the param map is not null map = request.getParameterMap(); System.err.println(map); assertFalse(map == null); assertTrue(map.isEmpty()); Enumeration names = request.getParameterNames(); assertFalse(names.hasMoreElements()); } return true; } }; //Send a request with query string with illegal hex code to cause //an exception parsing the params String request="GET /?param=%ZZaaa HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: text/html;charset=utf8\n"+ "\n"; String responses=_connector.getResponses(request); assertTrue(responses.startsWith("HTTP/1.1 200")); } @Test public void testBadUtf8ParamExtraction() throws Exception { _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) { String value=request.getParameter("param"); return value.startsWith("aaa") && value.endsWith("bb"); } }; //Send a request with query string with illegal hex code to cause //an exception parsing the params String request="GET /?param=aaa%E7bbb HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: text/html;charset=utf8\n"+ "\n"; String responses=_connector.getResponses(request); assertTrue(responses.startsWith("HTTP/1.1 200")); } @Test public void testInvalidHostHeader() throws Exception { // Use a contextHandler with vhosts to force call to Request.getServerName() ContextHandler handler = new ContextHandler(); handler.addVirtualHosts(new String[1]); _server.stop(); _server.setHandler(handler); _server.start(); // Request with illegal Host header String request="GET / HTTP/1.1\r\n"+ "Host: whatever.com:\r\n"+ "Content-Type: text/html;charset=utf8\n"+ "\n"; String responses=_connector.getResponses(request); assertTrue("400 Bad Request response expected",responses.startsWith("HTTP/1.1 400")); } @Test public void testContentTypeEncoding() throws Exception { final ArrayList<String> results = new ArrayList<String>(); _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) { results.add(request.getContentType()); results.add(request.getCharacterEncoding()); return true; } }; _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Content-Type: text/test\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Content-Type: text/html;charset=utf8\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Content-Type: text/html; charset=\"utf8\"\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Content-Type: text/html; other=foo ; blah=\"charset=wrong;\" ; charset = \" x=z; \" ; more=values \n"+ "\n" ); int i=0; assertEquals("text/test",results.get(i++)); assertEquals(null,results.get(i++)); assertEquals("text/html;charset=utf8",results.get(i++)); assertEquals("utf8",results.get(i++)); assertEquals("text/html; charset=\"utf8\"",results.get(i++)); assertEquals("utf8",results.get(i++)); assertTrue(results.get(i++).startsWith("text/html")); assertEquals(" x=z; ",results.get(i++)); } @Test public void testHostPort() throws Exception { final ArrayList<String> results = new ArrayList<String>(); _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) { results.add(request.getRemoteAddr()); results.add(request.getServerName()); results.add(String.valueOf(request.getServerPort())); return true; } }; _connector.getResponses( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: myhost:8888\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: 1.2.3.4\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: 1.2.3.4:8888\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: [::1]\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: [::1]:8888\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: [::1]\n"+ "x-forwarded-for: remote\n"+ "x-forwarded-proto: https\n"+ "\n"+ "GET / HTTP/1.1\n"+ "Host: [::1]:8888\n"+ "x-forwarded-for: remote\n"+ "x-forwarded-proto: https\n"+ "\n" ); int i=0; assertEquals(null,results.get(i++)); assertEquals("myhost",results.get(i++)); assertEquals("80",results.get(i++)); assertEquals(null,results.get(i++)); assertEquals("myhost",results.get(i++)); assertEquals("8888",results.get(i++)); assertEquals(null,results.get(i++)); assertEquals("1.2.3.4",results.get(i++)); assertEquals("80",results.get(i++)); assertEquals(null,results.get(i++)); assertEquals("1.2.3.4",results.get(i++)); assertEquals("8888",results.get(i++)); assertEquals(null,results.get(i++)); assertEquals("[::1]",results.get(i++)); assertEquals("80",results.get(i++)); assertEquals(null,results.get(i++)); assertEquals("[::1]",results.get(i++)); assertEquals("8888",results.get(i++)); assertEquals("remote",results.get(i++)); assertEquals("[::1]",results.get(i++)); assertEquals("443",results.get(i++)); assertEquals("remote",results.get(i++)); assertEquals("[::1]",results.get(i++)); assertEquals("8888",results.get(i++)); } @Test public void testContent() throws Exception { final int[] length=new int[1]; _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) { assertEquals(request.getContentLength(), ((Request)request).getContentRead()); length[0]=request.getContentLength(); return true; } }; String content=""; for (int l=0;l<1025;l++) { String request="POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: text/test\r\n"+ "Content-Length: "+l+"\r\n"+ "Connection: close\r\n"+ "\r\n"+ content; String response = _connector.getResponses(request); assertEquals(l,length[0]); if (l>0) assertEquals(l,_handler._content.length()); content+="x"; } } @Test public void testPartialRead() throws Exception { Handler handler = new AbstractHandler() { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); Reader reader=request.getReader(); byte[] b=("read="+reader.read()+"\n").getBytes(StringUtil.__UTF8); response.setContentLength(b.length); response.getOutputStream().write(b); response.flushBuffer(); } }; _server.stop(); _server.setHandler(handler); _server.start(); String request="GET / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: text/plane\r\n"+ "Content-Length: "+10+"\r\n"+ "\r\n"+ "0123456789\r\n"+ "GET / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: text/plane\r\n"+ "Content-Length: "+10+"\r\n"+ "Connection: close\r\n"+ "\r\n"+ "ABCDEFGHIJ\r\n"; String responses = _connector.getResponses(request); int index=responses.indexOf("read="+(int)'0'); assertTrue(index>0); index=responses.indexOf("read="+(int)'A',index+7); assertTrue(index>0); } @Test public void testQueryAfterRead() throws Exception { Handler handler = new AbstractHandler() { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); Reader reader=request.getReader(); String in = IO.toString(reader); String param = request.getParameter("param"); byte[] b=("read='"+in+"' param="+param+"\n").getBytes(StringUtil.__UTF8); response.setContentLength(b.length); response.getOutputStream().write(b); response.flushBuffer(); } }; _server.stop(); _server.setHandler(handler); _server.start(); String request="POST /?param=right HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: application/x-www-form-urlencoded\r\n"+ "Content-Length: "+11+"\r\n"+ "Connection: close\r\n"+ "\r\n"+ "param=wrong\r\n"; String responses = _connector.getResponses(request); assertTrue(responses.indexOf("read='param=wrong' param=right")>0); } @Test public void testPartialInput() throws Exception { Handler handler = new AbstractHandler() { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); InputStream in=request.getInputStream(); byte[] b=("read="+in.read()+"\n").getBytes(StringUtil.__UTF8); response.setContentLength(b.length); response.getOutputStream().write(b); response.flushBuffer(); } }; _server.stop(); _server.setHandler(handler); _server.start(); String request="GET / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: text/plane\r\n"+ "Content-Length: "+10+"\r\n"+ "\r\n"+ "0123456789\r\n"+ "GET / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: text/plane\r\n"+ "Content-Length: "+10+"\r\n"+ "Connection: close\r\n"+ "\r\n"+ "ABCDEFGHIJ\r\n"; String responses = _connector.getResponses(request); int index=responses.indexOf("read="+(int)'0'); assertTrue(index>0); index=responses.indexOf("read="+(int)'A',index+7); assertTrue(index>0); } @Test public void testConnectionClose() throws Exception { String response; _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException { response.getOutputStream().println("Hello World"); return true; } }; response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertFalse(response.indexOf("Connection: close")>0); assertTrue(response.indexOf("Hello World")>0); response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Connection: close\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertTrue(response.indexOf("Connection: close")>0); assertTrue(response.indexOf("Hello World")>0); response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Connection: Other, close\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertTrue(response.indexOf("Connection: close")>0); assertTrue(response.indexOf("Hello World")>0); response=_connector.getResponses( "GET / HTTP/1.0\n"+ "Host: whatever\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertFalse(response.indexOf("Connection: close")>0); assertTrue(response.indexOf("Hello World")>0); response=_connector.getResponses( "GET / HTTP/1.0\n"+ "Host: whatever\n"+ "Connection: Other, close\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertTrue(response.indexOf("Hello World")>0); response=_connector.getResponses( "GET / HTTP/1.0\n"+ "Host: whatever\n"+ "Connection: Other,,keep-alive\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertTrue(response.indexOf("Connection: keep-alive")>0); assertTrue(response.indexOf("Hello World")>0); _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException { response.setHeader("Connection","TE"); response.addHeader("Connection","Other"); response.getOutputStream().println("Hello World"); return true; } }; response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertTrue(response.indexOf("Connection: TE,Other")>0); assertTrue(response.indexOf("Hello World")>0); response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Connection: close\n"+ "\n" ); assertTrue(response.indexOf("200")>0); assertTrue(response.indexOf("Connection: close")>0); assertTrue(response.indexOf("Hello World")>0); } @Test public void testCookies() throws Exception { final ArrayList<Cookie> cookies = new ArrayList<Cookie>(); _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException { javax.servlet.http.Cookie[] ca = request.getCookies(); if (ca!=null) cookies.addAll(Arrays.asList(ca)); response.getOutputStream().println("Hello World"); return true; } }; String response; cookies.clear(); response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "\n" ); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertEquals(0,cookies.size()); cookies.clear(); response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Cookie: name=quoted=\\\"value\\\"\n" + "\n" ); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertEquals(1,cookies.size()); assertEquals("name", cookies.get(0).getName()); assertEquals("quoted=\\\"value\\\"", cookies.get(0).getValue()); cookies.clear(); response=_connector.getResponses( "GET / HTTP/1.1\n"+ "Host: whatever\n"+ "Cookie: name=value; other=\"quoted=;value\"\n" + "\n" ); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertEquals(2,cookies.size()); assertEquals("name", cookies.get(0).getName()); assertEquals("value", cookies.get(0).getValue()); assertEquals("other", cookies.get(1).getName()); assertEquals("quoted=;value", cookies.get(1).getValue()); cookies.clear(); response=_connector.getResponses( "GET /other HTTP/1.1\n"+ "Host: whatever\n"+ "Other: header\n"+ "Cookie: name=value; other=\"quoted=;value\"\n" + "\n"+ "GET /other HTTP/1.1\n"+ "Host: whatever\n"+ "Other: header\n"+ "Cookie: name=value; other=\"quoted=;value\"\n" + "\n" ); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertEquals(4,cookies.size()); assertEquals("name", cookies.get(0).getName()); assertEquals("value", cookies.get(0).getValue()); assertEquals("other", cookies.get(1).getName()); assertEquals("quoted=;value", cookies.get(1).getValue()); assertSame(cookies.get(0), cookies.get(2)); assertSame(cookies.get(1), cookies.get(3)); cookies.clear(); response=_connector.getResponses( "GET /other HTTP/1.1\n"+ "Host: whatever\n"+ "Other: header\n"+ "Cookie: name=value; other=\"quoted=;value\"\n" + "\n"+ "GET /other HTTP/1.1\n"+ "Host: whatever\n"+ "Other: header\n"+ "Cookie: name=value; other=\"othervalue\"\n" + "\n" ); assertTrue(response.startsWith("HTTP/1.1 200 OK")); assertEquals(4,cookies.size()); assertEquals("name", cookies.get(0).getName()); assertEquals("value", cookies.get(0).getValue()); assertEquals("other", cookies.get(1).getName()); assertEquals("quoted=;value", cookies.get(1).getValue()); assertNotSame(cookies.get(0), cookies.get(2)); assertNotSame(cookies.get(1), cookies.get(3)); cookies.clear(); response=_connector.getResponses( "POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie: name0=value0; name1 = value1 ; \"\\\"name2\\\"\" = \"\\\"value2\\\"\" \n" + "Cookie: $Version=2; name3=value3=value3;$path=/path;$domain=acme.com;$port=8080, name4=; name5 = ; name6\n" + "Cookie: name7=value7;\n" + "Connection: close\r\n"+ "\r\n"); assertEquals("name0", cookies.get(0).getName()); assertEquals("value0", cookies.get(0).getValue()); assertEquals("name1", cookies.get(1).getName()); assertEquals("value1", cookies.get(1).getValue()); assertEquals("\"name2\"", cookies.get(2).getName()); assertEquals("\"value2\"", cookies.get(2).getValue()); assertEquals("name3", cookies.get(3).getName()); assertEquals("value3=value3", cookies.get(3).getValue()); assertEquals(2, cookies.get(3).getVersion()); assertEquals("/path", cookies.get(3).getPath()); assertEquals("acme.com", cookies.get(3).getDomain()); assertEquals("$port=8080", cookies.get(3).getComment()); assertEquals("name4", cookies.get(4).getName()); assertEquals("", cookies.get(4).getValue()); assertEquals("name5", cookies.get(5).getName()); assertEquals("", cookies.get(5).getValue()); assertEquals("name6", cookies.get(6).getName()); assertEquals("", cookies.get(6).getValue()); assertEquals("name7", cookies.get(7).getName()); assertEquals("value7", cookies.get(7).getValue()); } @Test public void testCookieLeak() throws Exception { final String[] cookie=new String[10]; _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) { for (int i=0;i<cookie.length; i++) cookie[i]=null; Cookie[] cookies = request.getCookies(); for (int i=0;cookies!=null && i<cookies.length; i++) { cookie[i]=cookies[i].getValue(); } return true; } }; String request="POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie: other=cookie\r\n"+ "\r\n" + "POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie: name=value\r\n"+ "Connection: close\r\n"+ "\r\n"; _connector.getResponses(request); assertEquals("value",cookie[0]); assertEquals(null,cookie[1]); request="POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie: name=value\r\n"+ "\r\n" + "POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie:\r\n"+ "Connection: close\r\n"+ "\r\n"; _connector.getResponses(request); assertEquals(null,cookie[0]); assertEquals(null,cookie[1]); request="POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie: name=value\r\n"+ "Cookie: other=cookie\r\n"+ "\r\n" + "POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Cookie: name=value\r\n"+ "Cookie:\r\n"+ "Connection: close\r\n"+ "\r\n"; _connector.getResponses(request); assertEquals("value",cookie[0]); assertEquals(null,cookie[1]); } @Test public void testHashDOS() throws Exception { _server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1); _server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000); // This file is not distributed - as it is dangerous File evil_keys = new File("/tmp/keys_mapping_to_zero_2m"); if (!evil_keys.exists()) { Log.info("testHashDOS skipped"); return; } BufferedReader in = new BufferedReader(new FileReader(evil_keys)); StringBuilder buf = new StringBuilder(4000000); String key=null; buf.append("a=b"); while((key=in.readLine())!=null) { buf.append("&").append(key).append("=").append("x"); } buf.append("&c=d"); _handler._checker = new RequestTester() { public boolean check(HttpServletRequest request,HttpServletResponse response) { return "b".equals(request.getParameter("a")) && request.getParameter("c")==null; } }; String request="POST / HTTP/1.1\r\n"+ "Host: whatever\r\n"+ "Content-Type: "+MimeTypes.FORM_ENCODED+"\r\n"+ "Content-Length: "+buf.length()+"\r\n"+ "Connection: close\r\n"+ "\r\n"+ buf; long start=System.currentTimeMillis(); String response = _connector.getResponses(request); assertTrue(response.contains("200 OK")); long now=System.currentTimeMillis(); assertTrue((now-start)<5000); } interface RequestTester { boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException; } private class RequestHandler extends AbstractHandler { private RequestTester _checker; private String _content; public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ((Request)request).setHandled(true); if (request.getContentLength()>0 && !MimeTypes.FORM_ENCODED.equals(request.getContentType())) _content=IO.toString(request.getInputStream()); if (_checker!=null && _checker.check(request,response)) response.setStatus(200); else response.sendError(500); } } }