Issue #258 (Http request to origin server over https proxy contains absolute URL)

Fixed by sending the request target in origin-form (and not in
absolute-form) when request is to a https server.
This commit is contained in:
Simone Bordet 2016-03-10 22:01:17 +01:00
parent 4039f00bda
commit 2af81781cd
2 changed files with 223 additions and 2 deletions

View File

@ -35,6 +35,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -88,7 +89,6 @@ public abstract class HttpConnection implements Connection
protected void normalizeRequest(Request request)
{
String method = request.getMethod();
HttpVersion version = request.getVersion();
HttpFields headers = request.getHeaders();
ContentProvider content = request.getContent();
@ -104,7 +104,7 @@ public abstract class HttpConnection implements Connection
URI uri = request.getURI();
if (proxy != null && uri != null)
if (proxy != null && !HttpScheme.HTTPS.is(request.getScheme()) && uri != null)
{
path = uri.toString();
request.path(path);

View File

@ -0,0 +1,221 @@
//
// ========================================================================
// 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.
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ForwardProxyServerTest
{
@Parameterized.Parameters
public static Object[] parameters()
{
return new Object[]{null, newSslContextFactory()};
}
@Rule
public final TestTracker tracker = new TestTracker();
private final SslContextFactory serverSslContextFactory;
private Server server;
private ServerConnector serverConnector;
private Server proxy;
private ServerConnector proxyConnector;
public ForwardProxyServerTest(SslContextFactory serverSslContextFactory)
{
this.serverSslContextFactory = serverSslContextFactory;
}
protected void startServer(ConnectionFactory connectionFactory) throws Exception
{
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
server = new Server(serverThreads);
serverConnector = new ServerConnector(server, serverSslContextFactory, connectionFactory);
server.addConnector(serverConnector);
server.start();
}
protected void startProxy() throws Exception
{
QueuedThreadPool proxyThreads = new QueuedThreadPool();
proxyThreads.setName("proxy");
proxy = new Server(proxyThreads);
proxyConnector = new ServerConnector(proxy);
proxy.addConnector(proxyConnector);
// Under Windows, it takes a while to detect that a connection
// attempt fails, so use an explicit timeout
ConnectHandler connectHandler = new ConnectHandler();
connectHandler.setConnectTimeout(1000);
proxy.setHandler(connectHandler);
ServletContextHandler proxyHandler = new ServletContextHandler(connectHandler, "/");
proxyHandler.addServlet(ProxyServlet.class, "/*");
proxy.start();
}
protected HttpProxy newHttpProxy()
{
return new HttpProxy("localhost", proxyConnector.getLocalPort());
}
private static SslContextFactory newSslContextFactory()
{
SslContextFactory sslContextFactory = new SslContextFactory();
String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
sslContextFactory.setKeyStorePath(keyStorePath);
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
return sslContextFactory;
}
@After
public void stop() throws Exception
{
stopProxy();
stopServer();
}
protected void stopServer() throws Exception
{
if (server != null)
{
server.stop();
server.join();
}
}
protected void stopProxy() throws Exception
{
if (proxy != null)
{
proxy.stop();
proxy.join();
}
}
@Test
public void testRequestTarget() throws Exception
{
startServer(new AbstractConnectionFactory("http/1.1")
{
@Override
public Connection newConnection(Connector connector, EndPoint endPoint)
{
return new AbstractConnection(endPoint, connector.getExecutor())
{
@Override
public void onOpen()
{
super.onOpen();
fillInterested();
}
@Override
public void onFillable()
{
try
{
// When using TLS, multiple reads are required.
ByteBuffer buffer = BufferUtil.allocate(1024);
int filled = 0;
while (filled == 0)
filled = getEndPoint().fill(buffer);
Utf8StringBuilder builder = new Utf8StringBuilder();
builder.append(buffer);
String request = builder.toString();
// ProxyServlet will receive an absolute URI from
// the client, and convert it to a relative URI.
// The ConnectHandler won't modify what the client
// sent, which must be a relative URI.
Assert.assertThat(request.length(), Matchers.greaterThan(0));
if (serverSslContextFactory == null)
Assert.assertFalse(request.contains("http://"));
else
Assert.assertFalse(request.contains("https://"));
String response = "" +
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 0\r\n" +
"\r\n";
getEndPoint().write(Callback.NOOP, ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8)));
}
catch (Throwable x)
{
x.printStackTrace();
close();
}
}
};
}
});
startProxy();
HttpClient httpClient = new HttpClient(newSslContextFactory());
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest("localhost", serverConnector.getLocalPort())
.scheme(serverSslContextFactory == null ? "http" : "https")
.method(HttpMethod.GET)
.path("/test")
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally
{
httpClient.stop();
}
}
}