Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2019-02-05 08:24:38 +11:00
commit 2405e5417d
13 changed files with 644 additions and 104 deletions

View File

@ -37,6 +37,7 @@ import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -309,7 +310,9 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
@Override
public void onSuccess(Response response)
{
client.getAuthenticationStore().addAuthenticationResult(authenticationResult);
int status = response.getStatus();
if (HttpStatus.isSuccess(status) || HttpStatus.isRedirection(status))
client.getAuthenticationStore().addAuthenticationResult(authenticationResult);
}
}
}

View File

@ -103,7 +103,8 @@ public class BasicAuthentication extends AbstractAuthentication
@Override
public void apply(Request request)
{
request.header(header, value);
if (!request.getHeaders().contains(header, value))
request.header(header, value);
}
@Override

View File

@ -18,13 +18,6 @@
package org.eclipse.jetty.client;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
import java.io.IOException;
import java.net.URI;
@ -38,7 +31,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntFunction;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -74,6 +67,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{
private String realm = "TestRealm";
@ -232,7 +232,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
private final AtomicInteger requests = new AtomicInteger();
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
if (requests.incrementAndGet() == 1)
@ -272,7 +272,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
startBasic(scenario, new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
if (request.getRequestURI().endsWith("/redirect"))
@ -369,6 +369,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
assertNotNull(response);
assertEquals(401, response.getStatus());
Authentication.Result authenticationResult = authenticationStore.findAuthenticationResult(uri);
assertNull(authenticationResult);
}
@ParameterizedTest
@ -401,15 +404,11 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
.scheme(scenario.getScheme())
.path("/secure")
.timeout(5, TimeUnit.SECONDS)
.send(new Response.CompleteListener()
.send(result ->
{
@Override
public void onComplete(Result result)
{
assertTrue(result.isFailed());
assertEquals(cause, result.getFailure().getMessage());
latch.countDown();
}
assertTrue(result.isFailed());
assertEquals(cause, result.getFailure().getMessage());
latch.countDown();
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
@ -480,7 +479,6 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void test_RequestFailsAfterResponse(Scenario scenario) throws Exception
@ -488,10 +486,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
startBasic(scenario, new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
IO.readBytes(jettyRequest.getInputStream());
IO.readBytes(jettyRequest.getInputStream());
}
});
@ -543,8 +540,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{
authLatch.await();
}
catch(InterruptedException e)
{}
catch(InterruptedException ignored)
{
}
// Trigger request failure.
throw new RuntimeException();
@ -636,28 +634,27 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
HeaderInfo headerInfo = aph.getHeaderInfo("Digest realm=\"thermostat\", qop=\"auth\", nonce=\"1523430383\"").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfo.getParameter("qop").equals("auth"));
assertTrue(headerInfo.getParameter("realm").equals("thermostat"));
assertTrue(headerInfo.getParameter("nonce").equals("1523430383"));
assertEquals("auth", headerInfo.getParameter("qop"));
assertEquals("thermostat", headerInfo.getParameter("realm"));
assertEquals("1523430383", headerInfo.getParameter("nonce"));
headerInfo = aph.getHeaderInfo("Digest qop=\"auth\", realm=\"thermostat\", nonce=\"1523430383\"").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfo.getParameter("qop").equals("auth"));
assertTrue(headerInfo.getParameter("realm").equals("thermostat"));
assertTrue(headerInfo.getParameter("nonce").equals("1523430383"));
assertEquals("auth", headerInfo.getParameter("qop"));
assertEquals("thermostat", headerInfo.getParameter("realm"));
assertEquals("1523430383", headerInfo.getParameter("nonce"));
headerInfo = aph.getHeaderInfo("Digest qop=\"auth\", nonce=\"1523430383\", realm=\"thermostat\"").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfo.getParameter("qop").equals("auth"));
assertTrue(headerInfo.getParameter("realm").equals("thermostat"));
assertTrue(headerInfo.getParameter("nonce").equals("1523430383"));
assertEquals("auth", headerInfo.getParameter("qop"));
assertEquals("thermostat", headerInfo.getParameter("realm"));
assertEquals("1523430383", headerInfo.getParameter("nonce"));
headerInfo = aph.getHeaderInfo("Digest qop=\"auth\", nonce=\"1523430383\"").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfo.getParameter("qop").equals("auth"));
assertTrue(headerInfo.getParameter("realm") == null);
assertTrue(headerInfo.getParameter("nonce").equals("1523430383"));
assertEquals("auth", headerInfo.getParameter("qop"));
assertNull(headerInfo.getParameter("realm"));
assertEquals("1523430383", headerInfo.getParameter("nonce"));
// test multiple authentications
List<HeaderInfo> headerInfoList = aph.getHeaderInfo("Digest qop=\"auth\", realm=\"thermostat\", nonce=\"1523430383\", "
@ -666,34 +663,34 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
+ "Digest qop=\"auth4\", nonce=\"3526435321\"");
assertTrue(headerInfoList.get(0).getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfoList.get(0).getParameter("qop").equals("auth"));
assertTrue(headerInfoList.get(0).getParameter("realm").equals("thermostat"));
assertTrue(headerInfoList.get(0).getParameter("nonce").equals("1523430383"));
assertEquals("auth", headerInfoList.get(0).getParameter("qop"));
assertEquals("thermostat", headerInfoList.get(0).getParameter("realm"));
assertEquals("1523430383", headerInfoList.get(0).getParameter("nonce"));
assertTrue(headerInfoList.get(1).getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfoList.get(1).getParameter("qop").equals("auth2"));
assertTrue(headerInfoList.get(1).getParameter("realm").equals("thermostat2"));
assertTrue(headerInfoList.get(1).getParameter("nonce").equals("4522530354"));
assertEquals("auth2", headerInfoList.get(1).getParameter("qop"));
assertEquals("thermostat2", headerInfoList.get(1).getParameter("realm"));
assertEquals("4522530354", headerInfoList.get(1).getParameter("nonce"));
assertTrue(headerInfoList.get(2).getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfoList.get(2).getParameter("qop").equals("auth3"));
assertTrue(headerInfoList.get(2).getParameter("realm").equals("thermostat3"));
assertTrue(headerInfoList.get(2).getParameter("nonce").equals("9523570528"));
assertEquals("auth3", headerInfoList.get(2).getParameter("qop"));
assertEquals("thermostat3", headerInfoList.get(2).getParameter("realm"));
assertEquals("9523570528", headerInfoList.get(2).getParameter("nonce"));
assertTrue(headerInfoList.get(3).getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfoList.get(3).getParameter("qop").equals("auth4"));
assertTrue(headerInfoList.get(3).getParameter("realm") == null);
assertTrue(headerInfoList.get(3).getParameter("nonce").equals("3526435321"));
assertEquals("auth4", headerInfoList.get(3).getParameter("qop"));
assertNull(headerInfoList.get(3).getParameter("realm"));
assertEquals("3526435321", headerInfoList.get(3).getParameter("nonce"));
List<HeaderInfo> headerInfos = aph.getHeaderInfo("Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"");
assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Newauth"));
assertTrue(headerInfos.get(0).getParameter("realm").equals("apps"));
assertTrue(headerInfos.get(0).getParameter("type").equals("1"));
assertEquals("apps", headerInfos.get(0).getParameter("realm"));
assertEquals("1", headerInfos.get(0).getParameter("type"));
assertEquals(headerInfos.get(0).getParameter("title"),"Login to \"apps\"");
assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Basic"));
assertTrue(headerInfos.get(1).getParameter("realm").equals("simple"));
assertEquals("simple", headerInfos.get(1).getParameter("realm"));
}
@Test
@ -702,7 +699,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
HeaderInfo headerInfo = aph.getHeaderInfo("Scheme").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Scheme"));
assertTrue(headerInfo.getParameter("realm") == null);
assertNull(headerInfo.getParameter("realm"));
List<HeaderInfo> headerInfos = aph.getHeaderInfo("Scheme1 , Scheme2 , Scheme3");
assertEquals(3, headerInfos.size());
@ -712,51 +709,49 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
headerInfo = aph.getHeaderInfo("Scheme name=\"value\", other=\"value2\"").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Scheme"));
assertTrue(headerInfo.getParameter("name").equals("value"));
assertTrue(headerInfo.getParameter("other").equals("value2"));
assertEquals("value", headerInfo.getParameter("name"));
assertEquals("value2", headerInfo.getParameter("other"));
headerInfo = aph.getHeaderInfo("Scheme name = value , other = \"value2\" ").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Scheme"));
assertTrue(headerInfo.getParameter("name").equals("value"));
assertTrue(headerInfo.getParameter("other").equals("value2"));
assertEquals("value", headerInfo.getParameter("name"));
assertEquals("value2", headerInfo.getParameter("other"));
headerInfos = aph.getHeaderInfo(", , , , ,,,Scheme name=value, ,,Scheme2 name=value2,, ,,");
assertEquals(headerInfos.size(), 2);
assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Scheme"));
assertTrue(headerInfos.get(0).getParameter("nAmE").equals("value"));
assertEquals("value", headerInfos.get(0).getParameter("nAmE"));
assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Scheme2"));
headerInfos = aph.getHeaderInfo("Scheme name=value, Scheme2 name=value2");
assertEquals(headerInfos.size(), 2);
assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Scheme"));
assertTrue(headerInfos.get(0).getParameter("nAmE").equals("value"));
assertEquals("value", headerInfos.get(0).getParameter("nAmE"));
assertThat(headerInfos.get(1).getType(), equalToIgnoringCase("Scheme2"));
assertTrue(headerInfos.get(1).getParameter("nAmE").equals("value2"));
assertEquals("value2", headerInfos.get(1).getParameter("nAmE"));
headerInfos = aph.getHeaderInfo("Scheme , ,, ,, name=value, Scheme2 name=value2");
assertEquals(headerInfos.size(), 2);
assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Scheme"));
assertTrue(headerInfos.get(0).getParameter("name").equals("value"));
assertEquals("value", headerInfos.get(0).getParameter("name"));
assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Scheme2"));
assertTrue(headerInfos.get(1).getParameter("name").equals("value2"));
assertEquals("value2", headerInfos.get(1).getParameter("name"));
//Negotiate with base64 Content
headerInfo = aph.getHeaderInfo("Negotiate TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw==").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Negotiate"));
assertTrue(headerInfo.getBase64().equals("TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw=="));
assertEquals("TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAFAs4OAAAADw==", headerInfo.getBase64());
headerInfos = aph.getHeaderInfo("Negotiate TlRMTVNTUAABAAAAAAAAAFAs4OAAAADw==, "
+ "Negotiate YIIJvwYGKwYBBQUCoIIJszCCCa+gJDAi=");
assertTrue(headerInfos.get(0).getType().equalsIgnoreCase("Negotiate"));
assertTrue(headerInfos.get(0).getBase64().equals("TlRMTVNTUAABAAAAAAAAAFAs4OAAAADw=="));
assertEquals("TlRMTVNTUAABAAAAAAAAAFAs4OAAAADw==", headerInfos.get(0).getBase64());
assertTrue(headerInfos.get(1).getType().equalsIgnoreCase("Negotiate"));
assertTrue(headerInfos.get(1).getBase64().equals("YIIJvwYGKwYBBQUCoIIJszCCCa+gJDAi="));
assertEquals("YIIJvwYGKwYBBQUCoIIJszCCCa+gJDAi=", headerInfos.get(1).getBase64());
}
@Test
public void testEqualsInParam()
{
@ -765,10 +760,9 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
headerInfo = aph.getHeaderInfo("Digest realm=\"=the=rmo=stat=\", qop=\"=a=u=t=h=\", nonce=\"=1523430383=\"").get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfo.getParameter("qop").equals("=a=u=t=h="));
assertTrue(headerInfo.getParameter("realm").equals("=the=rmo=stat="));
assertTrue(headerInfo.getParameter("nonce").equals("=1523430383="));
assertEquals("=a=u=t=h=", headerInfo.getParameter("qop"));
assertEquals("=the=rmo=stat=", headerInfo.getParameter("realm"));
assertEquals("=1523430383=", headerInfo.getParameter("nonce"));
// test multiple authentications
List<HeaderInfo> headerInfoList = aph.getHeaderInfo("Digest qop=\"=au=th=\", realm=\"=ther=mostat=\", nonce=\"=152343=0383=\", "
@ -776,23 +770,23 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
+ "Digest qop=\"auth3=\", nonce=\"9523570528=\", realm=\"thermostat3=\", ");
assertTrue(headerInfoList.get(0).getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfoList.get(0).getParameter("qop").equals("=au=th="));
assertTrue(headerInfoList.get(0).getParameter("realm").equals("=ther=mostat="));
assertTrue(headerInfoList.get(0).getParameter("nonce").equals("=152343=0383="));
assertEquals("=au=th=", headerInfoList.get(0).getParameter("qop"));
assertEquals("=ther=mostat=", headerInfoList.get(0).getParameter("realm"));
assertEquals("=152343=0383=", headerInfoList.get(0).getParameter("nonce"));
assertTrue(headerInfoList.get(1).getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfoList.get(1).getParameter("qop").equals("=auth2"));
assertTrue(headerInfoList.get(1).getParameter("realm").equals("=thermostat2"));
assertTrue(headerInfoList.get(1).getParameter("nonce").equals("=4522530354"));
assertEquals("=auth2", headerInfoList.get(1).getParameter("qop"));
assertEquals("=thermostat2", headerInfoList.get(1).getParameter("realm"));
assertEquals("=4522530354", headerInfoList.get(1).getParameter("nonce"));
assertTrue(headerInfoList.get(2).getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfoList.get(2).getParameter("qop").equals("auth3="));
assertTrue(headerInfoList.get(2).getParameter("realm").equals("thermostat3="));
assertTrue(headerInfoList.get(2).getParameter("nonce").equals("9523570528="));
assertEquals("auth3=", headerInfoList.get(2).getParameter("qop"));
assertEquals("thermostat3=", headerInfoList.get(2).getParameter("realm"));
assertEquals("9523570528=", headerInfoList.get(2).getParameter("nonce"));
}
@Test
public void testSingleChallangeLooksLikeMultipleChallenge()
public void testSingleChallengeLooksLikeMultipleChallenges()
{
AuthenticationProtocolHandler aph = new WWWAuthenticationProtocolHandler(client);
List<HeaderInfo> headerInfoList = aph.getHeaderInfo("Digest param=\",f \"");
@ -803,8 +797,8 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
HeaderInfo headerInfo = headerInfoList.get(0);
assertTrue(headerInfo.getType().equalsIgnoreCase("Digest"));
assertTrue(headerInfo.getParameter("qop").equals(",Digest realm=hello"));
assertTrue(headerInfo.getParameter("realm").equals("thermostat"));
assertEquals(",Digest realm=hello", headerInfo.getParameter("qop"));
assertEquals("thermostat", headerInfo.getParameter("realm"));
assertEquals(headerInfo.getParameter("nonce"), "1523430383=");
}
}

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
@ -41,6 +39,8 @@ import org.eclipse.jetty.util.B64Code;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class HttpClientProxyTest extends AbstractHttpClientServerTest
{
@ParameterizedTest
@ -315,7 +315,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
assertEquals(status, response1.getStatus());
assertEquals(3, requests.get());
// Make again the request, authentication is cached, expect 204.
// Make again the request, only the server authentication is cached, expect 407 + 204.
requests.set(0);
ContentResponse response2 = client.newRequest(serverHost, serverPort)
.scheme(scenario.getScheme())
@ -323,7 +323,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
.send();
assertEquals(status, response2.getStatus());
assertEquals(1, requests.get());
assertEquals(2, requests.get());
}
@ParameterizedTest

View File

@ -424,8 +424,9 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
{
Connection old_connection = getConnection();
ByteBuffer prefilled = (old_connection instanceof Connection.UpgradeFrom)
?((Connection.UpgradeFrom)old_connection).onUpgradeFrom():null;
ByteBuffer buffer = (old_connection instanceof Connection.UpgradeFrom) ?
((Connection.UpgradeFrom)old_connection).onUpgradeFrom() :
null;
old_connection.onClose();
old_connection.getEndPoint().setConnection(newConnection);
@ -434,9 +435,9 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint
this, old_connection, newConnection, BufferUtil.toDetailString(prefilled));
if (newConnection instanceof Connection.UpgradeTo)
((Connection.UpgradeTo)newConnection).onUpgradeTo(prefilled);
else if (BufferUtil.hasContent(prefilled))
throw new IllegalStateException();
((Connection.UpgradeTo)newConnection).onUpgradeTo(buffer);
else if (BufferUtil.hasContent(buffer))
throw new IllegalStateException("Cannot upgrade: " + newConnection + " does not implement " + Connection.UpgradeTo.class.getName());
newConnection.onOpen();
}

View File

@ -35,6 +35,7 @@ import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@ -60,6 +61,20 @@ import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
private static final Logger LOG = Log.getLogger(ManagedSelector.class);
private static final boolean FORCE_SELECT_NOW;
static
{
String property = System.getProperty("org.eclipse.jetty.io.forceSelectNow");
if (property != null)
{
FORCE_SELECT_NOW = Boolean.parseBoolean(property);
}
else
{
property = System.getProperty("os.name");
FORCE_SELECT_NOW = property != null && property.toLowerCase(Locale.ENGLISH).contains("windows");
}
}
private final AtomicBoolean _started = new AtomicBoolean(false);
private boolean _selecting = false;
@ -457,7 +472,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
if (Thread.interrupted() && !isRunning())
throw new ClosedSelectorException();
selected = selector.selectNow();
if (FORCE_SELECT_NOW)
selected = selector.selectNow();
}
if (LOG.isDebugEnabled())
LOG.debug("Selector {} woken up from select, {}/{}/{} selected", selector, selected, selector.selectedKeys().size(), selector.keys().size());

View File

@ -76,7 +76,7 @@ import org.eclipse.jetty.util.thread.Invocable;
* be called again and make another best effort attempt to progress the connection.
*
*/
public class SslConnection extends AbstractConnection
public class SslConnection extends AbstractConnection implements Connection.UpgradeTo
{
private static final Logger LOG = Log.getLogger(SslConnection.class);
private static final String TLS_1_3 = "TLSv1.3";
@ -260,6 +260,22 @@ public class SslConnection extends AbstractConnection
this._allowMissingCloseMessage = allowMissingCloseMessage;
}
private void acquireEncryptedInput()
{
if (_encryptedInput == null)
_encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
}
@Override
public void onUpgradeTo(ByteBuffer buffer)
{
if (BufferUtil.hasContent(buffer))
{
acquireEncryptedInput();
BufferUtil.append(_encryptedInput, buffer);
}
}
@Override
public void onOpen()
{
@ -526,8 +542,7 @@ public class SslConnection extends AbstractConnection
throw new IllegalStateException("Unexpected HandshakeStatus " + status);
}
if (_encryptedInput == null)
_encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
acquireEncryptedInput();
// can we use the passed buffer if it is big enough
ByteBuffer app_in;

View File

@ -29,7 +29,10 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
@ -54,6 +57,8 @@ import org.eclipse.jetty.util.StringUtil;
*/
public class ForwardedRequestCustomizer implements Customizer
{
private static final Logger LOG = Log.getLogger(ForwardedRequestCustomizer.class);
private HostPortHttpField _forcedHost;
private String _forwardedHeader = HttpHeader.FORWARDED.toString();
private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
@ -297,7 +302,7 @@ public class ForwardedRequestCustomizer implements Customizer
RFC7239 rfc7239 = null;
String forwardedHost = null;
String forwardedServer = null;
String forwardedFor = null;
HostPort forwardedFor = null;
String forwardedProto = null;
String forwardedHttps = null;
@ -333,7 +338,7 @@ public class ForwardedRequestCustomizer implements Customizer
forwardedServer = getLeftMost(field.getValue());
if (forwardedFor==null && _forwardedForHeader!=null && _forwardedForHeader.equalsIgnoreCase(name))
forwardedFor = getLeftMost(field.getValue());
forwardedFor = getRemoteAddr(field.getValue());
if (forwardedProto==null && _forwardedProtoHeader!=null && _forwardedProtoHeader.equalsIgnoreCase(name))
forwardedProto = getLeftMost(field.getValue());
@ -389,7 +394,7 @@ public class ForwardedRequestCustomizer implements Customizer
}
else if (forwardedFor != null)
{
request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor.getHost(), (forwardedFor.getPort() > 0) ? forwardedFor.getPort() : request.getRemotePort()));
}
// handle protocol identifier
@ -430,6 +435,26 @@ public class ForwardedRequestCustomizer implements Customizer
// The left-most value is the farthest downstream client
return headerValue.substring(0,commaIndex).trim();
}
protected HostPort getRemoteAddr(String headerValue)
{
String leftMost = getLeftMost(headerValue);
if (leftMost == null)
{
return null;
}
try
{
return new HostPort(leftMost);
}
catch (Exception e)
{
// failed to parse in host[:port] format
LOG.ignore(e);
return null;
}
}
@Override
public String toString()

View File

@ -50,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger;
/**
* <p>A {@link Connection} that handles the HTTP protocol.</p>
*/
public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, Connection.UpgradeFrom, WriteFlusher.Listener
public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, WriteFlusher.Listener, Connection.UpgradeFrom, Connection.UpgradeTo
{
private static final Logger LOG = Log.getLogger(HttpConnection.class);
public static final HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE.asString());
@ -193,6 +193,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
return null;
}
@Override
public void onUpgradeTo(ByteBuffer buffer)
{
if (BufferUtil.hasContent(buffer))
BufferUtil.append(getRequestBuffer(), buffer);
}
@Override
public void onFlushed(long bytes) throws IOException
{
@ -497,7 +504,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
public void onOpen()
{
super.onOpen();
fillInterested();
if (isRequestBufferEmpty())
fillInterested();
else
getExecutor().execute(this);
}
@Override

View File

@ -0,0 +1,226 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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 java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* <p>A ConnectionFactory whose connections detect whether the first bytes are
* TLS bytes and upgrades to either a TLS connection or to another configurable
* connection.</p>
*/
public class OptionalSslConnectionFactory extends AbstractConnectionFactory
{
private static final Logger LOG = Log.getLogger(OptionalSslConnection.class);
private static final int TLS_ALERT_FRAME_TYPE = 0x15;
private static final int TLS_HANDSHAKE_FRAME_TYPE = 0x16;
private static final int TLS_MAJOR_VERSION = 3;
private final SslConnectionFactory sslConnectionFactory;
private final String otherProtocol;
/**
* <p>Creates a new ConnectionFactory whose connections can upgrade to TLS or another protocol.</p>
* <p>If {@code otherProtocol} is {@code null}, and the first bytes are not TLS, then
* {@link #otherProtocol(ByteBuffer, EndPoint)} is called.</p>
*
* @param sslConnectionFactory The SslConnectionFactory to use if the first bytes are TLS
* @param otherProtocol the protocol of the ConnectionFactory to use if the first bytes are not TLS,
* or null to explicitly handle the non-TLS case
*/
public OptionalSslConnectionFactory(SslConnectionFactory sslConnectionFactory, String otherProtocol)
{
super("ssl|other");
this.sslConnectionFactory = sslConnectionFactory;
this.otherProtocol = otherProtocol;
}
@Override
public Connection newConnection(Connector connector, EndPoint endPoint)
{
return configure(new OptionalSslConnection(endPoint, connector), connector, endPoint);
}
/**
* @param buffer The buffer with the first bytes of the connection
* @return whether the bytes seem TLS bytes
*/
protected boolean seemsTLS(ByteBuffer buffer)
{
int tlsFrameType = buffer.get(0) & 0xFF;
int tlsMajorVersion = buffer.get(1) & 0xFF;
return (tlsFrameType == TLS_HANDSHAKE_FRAME_TYPE || tlsFrameType == TLS_ALERT_FRAME_TYPE) && tlsMajorVersion == TLS_MAJOR_VERSION;
}
/**
* <p>Callback method invoked when {@code otherProtocol} is {@code null}
* and the first bytes are not TLS.</p>
* <p>This typically happens when a client is trying to connect to a TLS
* port using the {@code http} scheme (and not the {@code https} scheme).</p>
*
* @param buffer The buffer with the first bytes of the connection
* @param endPoint The connection EndPoint object
* @see #seemsTLS(ByteBuffer)
*/
protected void otherProtocol(ByteBuffer buffer, EndPoint endPoint)
{
// There are always at least 2 bytes.
int byte1 = buffer.get(0) & 0xFF;
int byte2 = buffer.get(1) & 0xFF;
if (byte1 == 'G' && byte2 == 'E')
{
// Plain text HTTP to a HTTPS port,
// write a minimal response.
String body = "" +
"<!DOCTYPE html>\r\n" +
"<html>\r\n" +
"<head><title>Bad Request</title></head>\r\n" +
"<body>" +
"<h1>Bad Request</h1>" +
"<p>HTTP request to HTTPS port</p>" +
"</body>\r\n" +
"</html>";
String response = "" +
"HTTP/1.1 400 Bad Request\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: " + body.length() + "\r\n" +
"Connection: close\r\n" +
"\r\n" +
body;
Callback.Completable completable = new Callback.Completable();
endPoint.write(completable, ByteBuffer.wrap(response.getBytes(StandardCharsets.US_ASCII)));
completable.whenComplete((r, x) -> endPoint.close());
}
else
{
endPoint.close();
}
}
private class OptionalSslConnection extends AbstractConnection implements Connection.UpgradeFrom
{
private final Connector connector;
private final ByteBuffer buffer;
public OptionalSslConnection(EndPoint endPoint, Connector connector)
{
super(endPoint, connector.getExecutor());
this.connector = connector;
this.buffer = BufferUtil.allocateDirect(1536);
}
@Override
public void onOpen()
{
super.onOpen();
fillInterested();
}
@Override
public void onFillable()
{
try
{
while (true)
{
int filled = getEndPoint().fill(buffer);
if (filled > 0)
{
// Always have at least 2 bytes.
if (BufferUtil.length(buffer) >= 2)
{
upgrade(buffer);
break;
}
}
else if (filled == 0)
{
fillInterested();
break;
}
else
{
close();
break;
}
}
}
catch (IOException x)
{
LOG.warn(x);
close();
}
}
@Override
public ByteBuffer onUpgradeFrom()
{
return buffer;
}
private void upgrade(ByteBuffer buffer)
{
if (LOG.isDebugEnabled())
LOG.debug("Read {}", BufferUtil.toDetailString(buffer));
EndPoint endPoint = getEndPoint();
if (seemsTLS(buffer))
{
if (LOG.isDebugEnabled())
LOG.debug("Detected TLS bytes, upgrading to {}", sslConnectionFactory);
endPoint.upgrade(sslConnectionFactory.newConnection(connector, endPoint));
}
else
{
if (otherProtocol != null)
{
ConnectionFactory connectionFactory = connector.getConnectionFactory(otherProtocol);
if (connectionFactory != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Detected non-TLS bytes, upgrading to {}", connectionFactory);
Connection next = connectionFactory.newConnection(connector, endPoint);
endPoint.upgrade(next);
}
else
{
LOG.warn("Missing {} {} in {}", otherProtocol, ConnectionFactory.class.getSimpleName(), connector);
close();
}
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Detected non-TLS bytes, but no other protocol to upgrade to");
otherProtocol(buffer, endPoint);
}
}
}
}
}

View File

@ -233,6 +233,38 @@ public class ForwardedRequestCustomizerTest
assertEquals("0",_results.poll());
}
@Test
public void testForIpv4WithPort() throws Exception
{
String response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"X-Forwarded-For: 10.9.8.7:1111,6.5.4.3:2222\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("1111",_results.poll());
}
@Test
public void testForIpv6WithPort() throws Exception
{
String response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"X-Forwarded-For: [2001:db8:cafe::17]:1111,6.5.4.3:2222\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("1111",_results.poll());
}
@Test
public void testLegacyProto() throws Exception
{

View File

@ -0,0 +1,216 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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 java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class OptionalSslConnectionTest
{
private Server server;
private ServerConnector connector;
private void startServer(Function<SslConnectionFactory, OptionalSslConnectionFactory> configFn, Handler handler) throws Exception
{
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
server = new Server(serverThreads);
String keystore = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keystore);
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
HttpConfiguration httpConfig = new HttpConfiguration();
HttpConnectionFactory http = new HttpConnectionFactory(httpConfig);
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, http.getProtocol());
OptionalSslConnectionFactory sslOrOther = configFn.apply(ssl);
connector = new ServerConnector(server, 1, 1, sslOrOther, ssl, http);
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
@AfterEach
public void stopServer() throws Exception
{
if (server != null)
server.stop();
}
private OptionalSslConnectionFactory optionalSsl(SslConnectionFactory ssl)
{
return new OptionalSslConnectionFactory(ssl, ssl.getNextProtocol());
}
private OptionalSslConnectionFactory optionalSslNoOtherProtocol(SslConnectionFactory ssl)
{
return new OptionalSslConnectionFactory(ssl, null);
}
@Test
public void testOptionalSslConnection() throws Exception
{
startServer(this::optionalSsl, new EmptyServerHandler());
String request = "" +
"GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n";
byte[] requestBytes = request.getBytes(StandardCharsets.US_ASCII);
// Try first a plain text connection.
try (Socket plain = new Socket())
{
plain.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000);
OutputStream plainOutput = plain.getOutputStream();
plainOutput.write(requestBytes);
plainOutput.flush();
plain.setSoTimeout(5000);
InputStream plainInput = plain.getInputStream();
HttpTester.Response response = HttpTester.parseResponse(plainInput);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
}
// Then try a SSL connection.
SslContextFactory sslContextFactory = new SslContextFactory(true);
sslContextFactory.start();
try (Socket ssl = sslContextFactory.newSslSocket())
{
ssl.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000);
OutputStream sslOutput = ssl.getOutputStream();
sslOutput.write(requestBytes);
sslOutput.flush();
ssl.setSoTimeout(5000);
InputStream sslInput = ssl.getInputStream();
HttpTester.Response response = HttpTester.parseResponse(sslInput);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally
{
sslContextFactory.stop();
}
}
@Test
public void testOptionalSslConnectionWithOnlyOneByteShouldIdleTimeout() throws Exception
{
startServer(this::optionalSsl, new EmptyServerHandler());
long idleTimeout = 1000;
connector.setIdleTimeout(idleTimeout);
try (Socket socket = new Socket())
{
socket.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000);
OutputStream output = socket.getOutputStream();
output.write(0x16);
output.flush();
socket.setSoTimeout((int)(2 * idleTimeout));
InputStream input = socket.getInputStream();
int read = input.read();
assertEquals(-1, read);
}
}
@Test
public void testOptionalSslConnectionWithUnknownBytes() throws Exception
{
startServer(this::optionalSslNoOtherProtocol, new EmptyServerHandler());
try (Socket socket = new Socket())
{
socket.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000);
OutputStream output = socket.getOutputStream();
output.write(0x00);
output.flush();
Thread.sleep(500);
output.write(0x00);
output.flush();
socket.setSoTimeout(5000);
InputStream input = socket.getInputStream();
int read = input.read();
assertEquals(-1, read);
}
}
@Test
public void testOptionalSslConnectionWithHTTPBytes() throws Exception
{
startServer(this::optionalSslNoOtherProtocol, new EmptyServerHandler());
String request = "" +
"GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n";
byte[] requestBytes = request.getBytes(StandardCharsets.US_ASCII);
// Send a plain text HTTP request to SSL port,
// we should get back a minimal HTTP response.
try (Socket socket = new Socket())
{
socket.connect(new InetSocketAddress("localhost", connector.getLocalPort()), 1000);
OutputStream output = socket.getOutputStream();
output.write(requestBytes);
output.flush();
socket.setSoTimeout(5000);
InputStream input = socket.getInputStream();
HttpTester.Response response = HttpTester.parseResponse(input);
assertNotNull(response);
assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus());
}
}
private static class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler
{
@Override
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
}
}
}

View File

@ -1,4 +1,5 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.server.ConnectionLimit.LEVEL=DEBUG
#org.eclipse.jetty.server.AcceptRateLimit.LEVEL=DEBUG
#org.eclipse.jetty.server.AcceptRateLimit.LEVEL=DEBUG