| // |
| // ======================================================================== |
| // Copyright (c) 1995-2016 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. |
| // ======================================================================== |
| // |
| |
| // JettyTest.java -- |
| // |
| // Junit test that shows the Jetty SSL bug. |
| // |
| |
| package org.eclipse.jetty.server.ssl; |
| |
| import static org.hamcrest.Matchers.greaterThan; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertThat; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.net.HttpURLConnection; |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.net.SocketTimeoutException; |
| import java.net.URL; |
| |
| import javax.net.ssl.HostnameVerifier; |
| import javax.net.ssl.HttpsURLConnection; |
| import javax.net.ssl.SSLContext; |
| import javax.net.ssl.SSLSession; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletOutputStream; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.jetty.server.HttpConnectionFactory; |
| import org.eclipse.jetty.server.Request; |
| import org.eclipse.jetty.server.Server; |
| import org.eclipse.jetty.server.ServerConnector; |
| import org.eclipse.jetty.server.handler.AbstractHandler; |
| import org.eclipse.jetty.toolchain.test.MavenTestingUtils; |
| import org.eclipse.jetty.util.IO; |
| import org.eclipse.jetty.util.ssl.SslContextFactory; |
| import org.hamcrest.Matchers; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * |
| */ |
| public class SSLEngineTest |
| { |
| // Useful constants |
| private static final String HELLO_WORLD="Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n"; |
| private static final String JETTY_VERSION= Server.getVersion(); |
| private static final String PROTOCOL_VERSION="2.0"; |
| |
| /** The request. */ |
| private static final String REQUEST0_HEADER="POST /r0 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Content-Length: "; |
| private static final String REQUEST1_HEADER="POST /r1 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Connection: close\n"+"Content-Length: "; |
| private static final String REQUEST_CONTENT= |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+ |
| "<requests xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"+ |
| " xsi:noNamespaceSchemaLocation=\"commander.xsd\" version=\""+PROTOCOL_VERSION+"\">\n"+ |
| "</requests>"; |
| |
| private static final String REQUEST0=REQUEST0_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT; |
| private static final String REQUEST1=REQUEST1_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT; |
| |
| /** The expected response. */ |
| private static final String RESPONSE0="HTTP/1.1 200 OK\n"+"Content-Length: "+HELLO_WORLD.length()+"\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD; |
| private static final String RESPONSE1="HTTP/1.1 200 OK\n"+"Connection: close\n"+"Server: Jetty("+JETTY_VERSION+")\n"+'\n'+HELLO_WORLD; |
| |
| private static final int BODY_SIZE=300; |
| |
| private Server server; |
| private ServerConnector connector; |
| |
| |
| @Before |
| public void startServer() throws Exception |
| { |
| String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); |
| SslContextFactory sslContextFactory = new SslContextFactory(); |
| sslContextFactory.setKeyStorePath(keystore); |
| sslContextFactory.setKeyStorePassword("storepwd"); |
| sslContextFactory.setKeyManagerPassword("keypwd"); |
| |
| server=new Server(); |
| HttpConnectionFactory http = new HttpConnectionFactory(); |
| http.setInputBufferSize(512); |
| http.getHttpConfiguration().setRequestHeaderSize(512); |
| connector=new ServerConnector(server, sslContextFactory, http); |
| connector.setPort(0); |
| connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); |
| |
| server.addConnector(connector); |
| } |
| |
| @After |
| public void stopServer() throws Exception |
| { |
| server.stop(); |
| server.join(); |
| } |
| |
| @Test |
| public void testHelloWorld() throws Exception |
| { |
| server.setHandler(new HelloWorldHandler()); |
| server.start(); |
| |
| SSLContext ctx=SSLContext.getInstance("TLS"); |
| ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom()); |
| |
| int port=connector.getLocalPort(); |
| |
| Socket client=ctx.getSocketFactory().createSocket("localhost",port); |
| OutputStream os=client.getOutputStream(); |
| |
| String request = |
| "GET / HTTP/1.1\r\n"+ |
| "Host: localhost:"+port+"\r\n"+ |
| "Connection: close\r\n"+ |
| "\r\n"; |
| |
| os.write(request.getBytes()); |
| os.flush(); |
| |
| String response = IO.toString(client.getInputStream()); |
| |
| assertThat(response, Matchers.containsString("200 OK")); |
| assertThat(response,Matchers.containsString(HELLO_WORLD)); |
| } |
| |
| @Test |
| public void testBigResponse() throws Exception |
| { |
| server.setHandler(new HelloWorldHandler()); |
| server.start(); |
| |
| SSLContext ctx=SSLContext.getInstance("TLS"); |
| ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom()); |
| |
| int port=connector.getLocalPort(); |
| |
| Socket client=ctx.getSocketFactory().createSocket("localhost",port); |
| OutputStream os=client.getOutputStream(); |
| |
| String request = |
| "GET /?dump=102400 HTTP/1.1\r\n"+ |
| "Host: localhost:"+port+"\r\n"+ |
| "Connection: close\r\n"+ |
| "\r\n"; |
| |
| os.write(request.getBytes()); |
| os.flush(); |
| |
| String response = IO.toString(client.getInputStream()); |
| |
| assertThat(response.length(),greaterThan(102400)); |
| } |
| |
| @Test |
| public void testRequestJettyHttps() throws Exception |
| { |
| server.setHandler(new HelloWorldHandler()); |
| server.start(); |
| |
| final int loops=10; |
| final int numConns=20; |
| |
| Socket[] client=new Socket[numConns]; |
| |
| SSLContext ctx=SSLContext.getInstance("SSLv3"); |
| ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom()); |
| |
| int port=connector.getLocalPort(); |
| |
| try |
| { |
| for (int l=0;l<loops;l++) |
| { |
| // System.err.print('.'); |
| try |
| { |
| for (int i=0; i<numConns; ++i) |
| { |
| // System.err.println("write:"+i); |
| client[i]=ctx.getSocketFactory().createSocket("localhost",port); |
| OutputStream os=client[i].getOutputStream(); |
| |
| os.write(REQUEST0.getBytes()); |
| os.write(REQUEST0.getBytes()); |
| os.flush(); |
| } |
| |
| for (int i=0; i<numConns; ++i) |
| { |
| // System.err.println("flush:"+i); |
| OutputStream os=client[i].getOutputStream(); |
| os.write(REQUEST1.getBytes()); |
| os.flush(); |
| } |
| |
| for (int i=0; i<numConns; ++i) |
| { |
| // System.err.println("read:"+i); |
| // Read the response. |
| String responses=readResponse(client[i]); |
| // Check the responses |
| assertEquals(String.format("responses loop=%d connection=%d",l,i),RESPONSE0+RESPONSE0+RESPONSE1,responses); |
| } |
| } |
| finally |
| { |
| for (int i=0; i<numConns; ++i) |
| { |
| if (client[i]!=null) |
| { |
| try |
| { |
| assertEquals(-1,client[i].getInputStream().read()); |
| } |
| catch(SocketException e) |
| { |
| } |
| } |
| } |
| } |
| } |
| } |
| finally |
| { |
| // System.err.println(); |
| } |
| } |
| |
| @Test |
| public void testURLConnectionChunkedPost() throws Exception |
| { |
| StreamHandler handler = new StreamHandler(); |
| server.setHandler(handler); |
| server.start(); |
| |
| SSLContext context = SSLContext.getInstance("SSL"); |
| context.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom()); |
| HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); |
| |
| URL url = new URL("https://localhost:"+connector.getLocalPort()+"/test"); |
| |
| HttpURLConnection conn = (HttpURLConnection)url.openConnection(); |
| if (conn instanceof HttpsURLConnection) |
| { |
| ((HttpsURLConnection)conn).setHostnameVerifier(new HostnameVerifier() |
| { |
| @Override |
| public boolean verify(String urlHostName, SSLSession session) |
| { |
| return true; |
| } |
| }); |
| } |
| |
| conn.setConnectTimeout(10000); |
| conn.setReadTimeout(100000); |
| conn.setDoInput(true); |
| conn.setDoOutput(true); |
| conn.setRequestMethod("POST"); |
| conn.setRequestProperty("Content-Type","text/plain"); |
| conn.setChunkedStreamingMode(128); |
| conn.connect(); |
| byte[] b = new byte[BODY_SIZE]; |
| for (int i = 0; i < BODY_SIZE; i++) |
| { |
| b[i] = 'x'; |
| } |
| OutputStream os = conn.getOutputStream(); |
| os.write(b); |
| os.flush(); |
| |
| int len = 0; |
| InputStream is = conn.getInputStream(); |
| int bytes=0; |
| while ((len = is.read(b)) > -1) |
| bytes+=len; |
| is.close(); |
| |
| assertEquals(BODY_SIZE,handler.bytes); |
| assertEquals(BODY_SIZE,bytes); |
| } |
| |
| /** |
| * Reads entire response from the client. Close the output. |
| * |
| * @param client Open client socket. |
| * @return The response string. |
| * @throws IOException in case of I/O errors |
| */ |
| private static String readResponse(Socket client) throws IOException |
| { |
| BufferedReader br=null; |
| StringBuilder sb=new StringBuilder(1000); |
| |
| try |
| { |
| client.setSoTimeout(5000); |
| br=new BufferedReader(new InputStreamReader(client.getInputStream())); |
| |
| String line; |
| |
| while ((line=br.readLine())!=null) |
| { |
| sb.append(line); |
| sb.append('\n'); |
| } |
| } |
| catch(SocketTimeoutException e) |
| { |
| System.err.println("Test timedout: "+e.toString()); |
| e.printStackTrace(); // added to see if we can get more info from failures on CI |
| } |
| finally |
| { |
| if (br!=null) |
| { |
| br.close(); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private static class HelloWorldHandler extends AbstractHandler |
| { |
| @Override |
| public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException |
| { |
| // System.err.println("HANDLE "+request.getRequestURI()); |
| String ssl_id = (String)request.getAttribute("javax.servlet.request.ssl_session_id"); |
| assertNotNull(ssl_id); |
| |
| if (request.getParameter("dump")!=null) |
| { |
| ServletOutputStream out=response.getOutputStream(); |
| byte[] buf = new byte[Integer.valueOf(request.getParameter("dump"))]; |
| // System.err.println("DUMP "+buf.length); |
| for (int i=0;i<buf.length;i++) |
| buf[i]=(byte)('0'+(i%10)); |
| out.write(buf); |
| out.close(); |
| } |
| else |
| { |
| PrintWriter out=response.getWriter(); |
| out.print(HELLO_WORLD); |
| out.close(); |
| } |
| } |
| } |
| |
| private static class StreamHandler extends AbstractHandler |
| { |
| private int bytes=0; |
| |
| public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException |
| { |
| response.setContentType("text/plain"); |
| response.setBufferSize(128); |
| byte[] b = new byte[BODY_SIZE]; |
| int len = 0; |
| InputStream is = request.getInputStream(); |
| while ((len = is.read(b)) > -1) |
| { |
| bytes+=len; |
| } |
| |
| OutputStream os = response.getOutputStream(); |
| for (int i = 0; i < BODY_SIZE; i++) |
| { |
| b[i] = 'x'; |
| } |
| os.write(b); |
| response.flushBuffer(); |
| } |
| } |
| |
| } |