// // ======================================================================== // 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.server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; public class ForwardedRequestCustomizerTest { private Server _server; private LocalConnector _connector; private RequestHandler _handler; final Deque<String> _results = new ArrayDeque<>(); final AtomicBoolean _wasSecure = new AtomicBoolean(false); final AtomicReference<String> _sslSession = new AtomicReference<>(); final AtomicReference<String> _sslCertificate = new AtomicReference<>(); ForwardedRequestCustomizer _customizer; @Before public void init() throws Exception { _server = new Server(); HttpConnectionFactory http = new HttpConnectionFactory(); http.setInputBufferSize(1024); http.getHttpConfiguration().setRequestHeaderSize(512); http.getHttpConfiguration().setResponseHeaderSize(512); http.getHttpConfiguration().setOutputBufferSize(2048); http.getHttpConfiguration().addCustomizer(_customizer=new ForwardedRequestCustomizer()); _connector = new LocalConnector(_server,http); _server.addConnector(_connector); _handler = new RequestHandler(); _server.setHandler(_handler); _handler._checker = new RequestTester() { @Override public boolean check(HttpServletRequest request,HttpServletResponse response) { _wasSecure.set(request.isSecure()); _sslSession.set(String.valueOf(request.getAttribute("javax.servlet.request.ssl_session_id"))); _sslCertificate.set(String.valueOf(request.getAttribute("javax.servlet.request.cipher_suite"))); _results.add(request.getScheme()); _results.add(request.getServerName()); _results.add(Integer.toString(request.getServerPort())); _results.add(request.getRemoteAddr()); _results.add(Integer.toString(request.getRemotePort())); return true; } }; _server.start(); } @After public void destroy() throws Exception { _server.stop(); _server.join(); } @Test public void testRFC7239_Examples_4() throws Exception { String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Forwarded: for=\"_gazonk\"\n"+ "Forwarded: For=\"[2001:db8:cafe::17]:4711\"\n"+ "Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43\n"+ "Forwarded: for=192.0.2.43, for=198.51.100.17\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("[2001:db8:cafe::17]",_results.poll()); assertEquals("4711",_results.poll()); } @Test public void testRFC7239_Examples_7_1() throws Exception { _connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Forwarded: for=192.0.2.43,for=\"[2001:db8:cafe::17]\",for=unknown\n"+ "\n"); _connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Forwarded: for=192.0.2.43, for=\"[2001:db8:cafe::17]\", for=unknown\n"+ "\n"); _connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Forwarded: for=192.0.2.43\n"+ "Forwarded: for=\"[2001:db8:cafe::17]\", for=unknown\n"+ "\n"); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("192.0.2.43",_results.poll()); assertEquals("0",_results.poll()); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("192.0.2.43",_results.poll()); assertEquals("0",_results.poll()); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("192.0.2.43",_results.poll()); assertEquals("0",_results.poll()); } @Test public void testRFC7239_Examples_7_4() throws Exception { _connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Forwarded: for=192.0.2.43, for=\"[2001:db8:cafe::17]\"\n"+ "\n"); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("192.0.2.43",_results.poll()); assertEquals("0",_results.poll()); } @Test public void testRFC7239_Examples_7_5() throws Exception { _connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Forwarded: for=192.0.2.43,for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com\n"+ "\n"); assertEquals("http",_results.poll()); assertEquals("example.com",_results.poll()); assertEquals("80",_results.poll()); assertEquals("192.0.2.43",_results.poll()); assertEquals("0",_results.poll()); } @Test public void testProto() throws Exception { String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "X-Forwarded-Proto: foobar\n"+ "Forwarded: proto=https\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("https",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("443",_results.poll()); assertEquals("0.0.0.0",_results.poll()); assertEquals("0",_results.poll()); } @Test public void testFor() throws Exception { String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "X-Forwarded-For: 10.9.8.7,6.5.4.3\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("10.9.8.7",_results.poll()); assertEquals("0",_results.poll()); } @Test public void testLegacyProto() throws Exception { String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "X-Proxied-Https: on\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("https",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("443",_results.poll()); assertEquals("0.0.0.0",_results.poll()); assertEquals("0",_results.poll()); assertTrue(_wasSecure.get()); } @Test public void testSslSession() throws Exception { _customizer.setSslIsSecure(false); String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Proxy-Ssl-Id: Wibble\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("0.0.0.0",_results.poll()); assertEquals("0",_results.poll()); assertFalse(_wasSecure.get()); assertEquals("Wibble",_sslSession.get()); _customizer.setSslIsSecure(true); response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Proxy-Ssl-Id: 0123456789abcdef\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("https",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("443",_results.poll()); assertEquals("0.0.0.0",_results.poll()); assertEquals("0",_results.poll()); assertTrue(_wasSecure.get()); assertEquals("0123456789abcdef",_sslSession.get()); } @Test public void testSslCertificate() throws Exception { _customizer.setSslIsSecure(false); String response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Proxy-auth-cert: Wibble\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("http",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("80",_results.poll()); assertEquals("0.0.0.0",_results.poll()); assertEquals("0",_results.poll()); assertFalse(_wasSecure.get()); assertEquals("Wibble",_sslCertificate.get()); _customizer.setSslIsSecure(true); response=_connector.getResponse( "GET / HTTP/1.1\n"+ "Host: myhost\n"+ "Proxy-auth-cert: 0123456789abcdef\n"+ "\n"); assertThat(response, Matchers.containsString("200 OK")); assertEquals("https",_results.poll()); assertEquals("myhost",_results.poll()); assertEquals("443",_results.poll()); assertEquals("0.0.0.0",_results.poll()); assertEquals("0",_results.poll()); assertTrue(_wasSecure.get()); assertEquals("0123456789abcdef",_sslCertificate.get()); } interface RequestTester { boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException; } private class RequestHandler extends AbstractHandler { private RequestTester _checker; @SuppressWarnings("unused") private String _content; @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ((Request)request).setHandled(true); if (request.getContentLength()>0 && !MimeTypes.Type.FORM_ENCODED.asString().equals(request.getContentType()) && !request.getContentType().startsWith("multipart/form-data")) _content=IO.toString(request.getInputStream()); if (_checker!=null && _checker.check(request,response)) response.setStatus(200); else response.sendError(500); } } }