Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.

This commit is contained in:
Simone Bordet 2020-05-21 18:36:36 +02:00
commit 3af43c8ac3
5 changed files with 1990 additions and 6 deletions

View File

@ -67,6 +67,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
@ -88,8 +89,9 @@ import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.LINUX;
import static org.junit.jupiter.api.condition.OS.WINDOWS; import static org.junit.jupiter.api.condition.OS.WINDOWS;
// This whole test is very specific to how TLS < 1.3 works. // Other JREs have slight differences in how TLS work
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10}) // and this test expects a very specific TLS behavior.
@EnabledOnJre(JRE.JAVA_11)
public class SslBytesServerTest extends SslBytesTest public class SslBytesServerTest extends SslBytesTest
{ {
private final AtomicInteger sslFills = new AtomicInteger(); private final AtomicInteger sslFills = new AtomicInteger();
@ -108,9 +110,9 @@ public class SslBytesServerTest extends SslBytesTest
@BeforeEach @BeforeEach
public void init() throws Exception public void init() throws Exception
{ {
QueuedThreadPool serverThreads = new QueuedThreadPool();
threadPool = Executors.newCachedThreadPool(); serverThreads.setName("server");
server = new Server(); server = new Server(serverThreads);
sslFills.set(0); sslFills.set(0);
sslFlushes.set(0); sslFlushes.set(0);
@ -119,6 +121,8 @@ public class SslBytesServerTest extends SslBytesTest
File keyStore = MavenTestingUtils.getTestResourceFile("keystore.p12"); File keyStore = MavenTestingUtils.getTestResourceFile("keystore.p12");
sslContextFactory = new SslContextFactory.Server(); sslContextFactory = new SslContextFactory.Server();
// This whole test is very specific to how TLS < 1.3 works.
sslContextFactory.setIncludeProtocols("TLSv1.2");
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath()); sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyStorePassword("storepwd");
@ -238,6 +242,7 @@ public class SslBytesServerTest extends SslBytesTest
sslContext = sslContextFactory.getSslContext(); sslContext = sslContextFactory.getSslContext();
threadPool = Executors.newCachedThreadPool();
proxy = new SimpleProxy(threadPool, "localhost", serverPort); proxy = new SimpleProxy(threadPool, "localhost", serverPort);
proxy.start(); proxy.start();
logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort); logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort);
@ -1128,6 +1133,66 @@ public class SslBytesServerTest extends SslBytesTest
client.close(); client.close();
} }
@Test
public void testRequestResponseServerIdleTimeoutClientResets() throws Exception
{
SSLSocket client = newClient();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
client.startHandshake();
assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
Future<Object> request = threadPool.submit(() ->
{
OutputStream clientOutput = client.getOutputStream();
clientOutput.write((
"GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n").getBytes(StandardCharsets.UTF_8));
clientOutput.flush();
return null;
});
// Application data
TLSRecord record = proxy.readFromClient();
proxy.flushToServer(record);
assertNull(request.get(5, TimeUnit.SECONDS));
// Application data
record = proxy.readFromServer();
assertEquals(TLSRecord.Type.APPLICATION, record.getType());
proxy.flushToClient(record);
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
String line = reader.readLine();
assertNotNull(line);
assertTrue(line.startsWith("HTTP/1.1 200 "));
while ((line = reader.readLine()) != null)
{
if (line.trim().length() == 0)
break;
}
// Wait for the server idle timeout.
Thread.sleep(idleTimeout);
// We expect that the server sends the TLS Alert.
record = proxy.readFromServer();
assertNotNull(record);
assertEquals(TLSRecord.Type.ALERT, record.getType());
// Send a RST to the server.
proxy.sendRSTToServer();
// Wait for the RST to be processed by the server.
Thread.sleep(1000);
// The server EndPoint must be closed.
assertFalse(serverEndPoint.get().isOpen());
client.close();
}
@Test @Test
@EnabledOnOs(LINUX) // see message below @EnabledOnOs(LINUX) // see message below
public void testRequestWithCloseAlertWithSplitBoundary() throws Exception public void testRequestWithCloseAlertWithSplitBoundary() throws Exception

View File

@ -234,6 +234,7 @@ public abstract class SslBytesTest
public void flushToServer(TLSRecord record, long sleep) throws Exception public void flushToServer(TLSRecord record, long sleep) throws Exception
{ {
logger.debug("P --> S {}", record);
if (record == null) if (record == null)
{ {
server.shutdownOutput(); server.shutdownOutput();
@ -272,6 +273,7 @@ public abstract class SslBytesTest
public void flushToClient(TLSRecord record) throws Exception public void flushToClient(TLSRecord record) throws Exception
{ {
logger.debug("C <-- P {}", record);
if (record == null) if (record == null)
{ {
client.shutdownOutput(); client.shutdownOutput();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,528 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util.ajax;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class AsyncJSONTest
{
private static AsyncJSON newAsyncJSON()
{
AsyncJSON.Factory factory = new AsyncJSON.Factory();
factory.setDetailedParseException(true);
return factory.newAsyncJSON();
}
@ParameterizedTest
@ValueSource(strings = {"|", "}", "]", "{]", "[}", "+", ".", "{} []"})
public void testParseInvalidJSON(String json)
{
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertThrows(IllegalArgumentException.class, () -> parser.parse(bytes));
assertTrue(parser.isEmpty());
// Parse byte by byte.
assertThrows(IllegalArgumentException.class, () ->
{
for (byte b : bytes)
{
parser.parse(new byte[]{b});
}
});
assertTrue(parser.isEmpty());
}
@ParameterizedTest(name = "[{index}] ''{0}'' -> ''{1}''")
@MethodSource("validStrings")
public void testParseString(String string, String expected)
{
String json = "\"${value}\"".replace("${value}", string);
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertTrue(parser.parse(bytes));
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
// Parse byte by byte.
for (int i = 0; i < bytes.length; ++i)
{
byte b = bytes[i];
if (i == bytes.length - 1)
assertTrue(parser.parse(new byte[]{b}));
else
assertFalse(parser.parse(new byte[]{b}));
}
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
}
public static List<Object[]> validStrings()
{
List<Object[]> result = new ArrayList<>();
result.add(new Object[]{"", ""});
result.add(new Object[]{" \t\r\n", " \t\r\n"});
result.add(new Object[]{"\u20AC", "\u20AC"}); // euro symbol
result.add(new Object[]{"\\u20AC", "\u20AC"}); // euro symbol
result.add(new Object[]{"/foo", "/foo"});
result.add(new Object[]{"123E+01", "123E+01"});
result.add(new Object[]{"A\\u20AC/foo\\t\\n", "A\u20AC/foo\t\n"}); // euro symbol
result.add(new Object[]{" ABC ", " ABC "});
return result;
}
@ParameterizedTest
@ValueSource(strings = {"\\u", "\\u0", "\\x"})
public void testParseInvalidString(String value)
{
String json = "\"${value}\"".replace("${value}", value);
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertThrows(IllegalArgumentException.class, () -> parser.parse(bytes));
assertTrue(parser.isEmpty());
// Parse byte by byte.
assertThrows(IllegalArgumentException.class, () ->
{
for (byte b : bytes)
{
parser.parse(new byte[]{b});
}
});
assertTrue(parser.isEmpty());
}
@ParameterizedTest(name = "[{index}] {0} -> {1}")
@MethodSource("validArrays")
public void testParseArray(String json, List<?> expected)
{
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertTrue(parser.parse(bytes));
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
ByteBuffer buffer = ByteBuffer.wrap(bytes);
assertTrue(parser.parse(buffer));
assertFalse(buffer.hasRemaining());
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
// Parse byte by byte.
for (byte b : bytes)
{
parser.parse(new byte[]{b});
}
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
}
public static List<Object[]> validArrays()
{
List<Object[]> result = new ArrayList<>();
List<Object> expected = Collections.emptyList();
result.add(new Object[]{"[]", expected});
result.add(new Object[]{"[] \n", expected});
expected = new ArrayList<>();
expected.add(Collections.emptyList());
result.add(new Object[]{"[[]]", expected});
expected = new ArrayList<>();
expected.add("first");
expected.add(5D);
expected.add(null);
expected.add(true);
expected.add(false);
expected.add(new HashMap<>());
HashMap<String, Object> last = new HashMap<>();
last.put("a", new ArrayList<>());
expected.add(last);
result.add(new Object[]{"[\"first\", 5E+0, null, true, false, {}, {\"a\":[]}]", expected});
return result;
}
@ParameterizedTest
@ValueSource(strings = {"[", "]", "[[,]", " [ 1,2,[ "})
public void testParseInvalidArray(String json)
{
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertThrows(IllegalArgumentException.class, () ->
{
parser.parse(bytes);
parser.complete();
});
assertTrue(parser.isEmpty());
// Parse byte by byte.
assertThrows(IllegalArgumentException.class, () ->
{
for (byte b : bytes)
{
parser.parse(new byte[]{b});
}
parser.complete();
});
assertTrue(parser.isEmpty());
}
@ParameterizedTest(name = "[{index}] {0} -> {1}")
@MethodSource("validObjects")
public void testParseObject(String json, Object expected)
{
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertTrue(parser.parse(bytes));
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
// Parse byte by byte.
for (int i = 0; i < bytes.length; ++i)
{
byte b = bytes[i];
if (i == bytes.length - 1)
{
assertTrue(parser.parse(new byte[]{b}));
}
else
{
assertFalse(parser.parse(new byte[]{b}));
}
}
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
}
public static List<Object[]> validObjects()
{
List<Object[]> result = new ArrayList<>();
HashMap<String, Object> expected = new HashMap<>();
result.add(new Object[]{"{}", expected});
expected = new HashMap<>();
expected.put("", 0L);
result.add(new Object[]{"{\"\":0}", expected});
expected = new HashMap<>();
expected.put("name", "value");
result.add(new Object[]{"{ \"name\": \"value\" }", expected});
expected = new HashMap<>();
expected.put("name", null);
expected.put("valid", true);
expected.put("secure", false);
expected.put("value", 42L);
result.add(new Object[]{
"{, \"name\": null, \"valid\": true\n , \"secure\": false\r\n,\n \"value\":42, }", expected
});
return result;
}
@ParameterizedTest
@ValueSource(strings = {"{", "}", "{{,}", "{:\"s\"}", "{[]:0}", "{1:0}", " {\": 0} ", "{\"a: \"b\"}"})
public void testParseInvalidObject(String json)
{
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertThrows(IllegalArgumentException.class, () ->
{
parser.parse(bytes);
parser.complete();
});
assertTrue(parser.isEmpty());
// Parse byte by byte.
assertThrows(IllegalArgumentException.class, () ->
{
for (byte b : bytes)
{
parser.parse(new byte[]{b});
}
parser.complete();
});
assertTrue(parser.isEmpty());
}
@ParameterizedTest(name = "[{index}] {0} -> {1}")
@MethodSource("validNumbers")
public void testParseNumber(String json, Number expected)
{
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
parser.parse(bytes);
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
ByteBuffer buffer = ByteBuffer.wrap(bytes);
parser.parse(buffer);
assertEquals(expected, parser.complete());
assertFalse(buffer.hasRemaining());
assertTrue(parser.isEmpty());
// Parse byte by byte.
for (byte b : bytes)
{
parser.parse(new byte[]{b});
}
assertEquals(expected, parser.complete());
assertTrue(parser.isEmpty());
}
public static List<Object[]> validNumbers()
{
List<Object[]> result = new ArrayList<>();
result.add(new Object[]{"0", 0L});
result.add(new Object[]{"-0", -0L});
result.add(new Object[]{"13\n", 13L});
result.add(new Object[]{"-42", -42L});
result.add(new Object[]{"123.456", 123.456D});
result.add(new Object[]{"-234.567", -234.567D});
result.add(new Object[]{"9e0", 9D});
result.add(new Object[]{"8E+1\t", 80D});
result.add(new Object[]{"-7E-2 ", -0.07D});
result.add(new Object[]{"70.5E-1", 7.05D});
return result;
}
@ParameterizedTest
@ValueSource(strings = {"--", "--1", ".5", "e0", "1a1", "3-7", "1+2", "1e0e1", "1.2.3"})
public void testParseInvalidNumber(String json)
{
byte[] bytes = json.getBytes(UTF_8);
AsyncJSON parser = newAsyncJSON();
// Parse the whole input.
assertThrows(IllegalArgumentException.class, () ->
{
parser.parse(bytes);
parser.complete();
});
assertTrue(parser.isEmpty());
// Parse byte by byte.
assertThrows(IllegalArgumentException.class, () ->
{
for (byte b : bytes)
{
parser.parse(new byte[]{b});
}
parser.complete();
});
assertTrue(parser.isEmpty());
}
@Test
public void testParseObjectWithConvertor()
{
AsyncJSON.Factory factory = new AsyncJSON.Factory();
CustomConvertor convertor = new CustomConvertor();
factory.putConvertor(CustomConvertor.class.getName(), convertor);
String json = "{" +
"\"f1\": {\"class\":\"" + CustomConvertible.class.getName() + "\", \"field\": \"value\"}," +
"\"f2\": {\"class\":\"" + CustomConvertor.class.getName() + "\"}" +
"}";
AsyncJSON parser = factory.newAsyncJSON();
assertTrue(parser.parse(UTF_8.encode(json)));
Map<String, Object> result = parser.complete();
Object value1 = result.get("f1");
assertTrue(value1 instanceof CustomConvertible);
assertEquals("value", ((CustomConvertible)value1).field);
Object value2 = result.get("f2");
assertTrue(value2 instanceof CustomConvertor.Custom);
assertSame(convertor, factory.removeConvertor(CustomConvertor.class.getName()));
assertTrue(parser.parse(UTF_8.encode(json)));
result = parser.complete();
value1 = result.get("f1");
assertTrue(value1 instanceof CustomConvertible);
assertEquals("value", ((CustomConvertible)value1).field);
value2 = result.get("f2");
assertTrue(value2 instanceof Map);
@SuppressWarnings("unchecked")
Map<String, Object> map2 = (Map<String, Object>)value2;
assertEquals(CustomConvertor.class.getName(), map2.get("class"));
}
public static class CustomConvertible implements JSON.Convertible
{
private Object field;
@Override
public void toJSON(JSON.Output out)
{
}
@Override
public void fromJSON(Map<String, Object> map)
{
this.field = map.get("field");
}
}
public static class CustomConvertor implements JSON.Convertor
{
@Override
public void toJSON(Object obj, JSON.Output out)
{
}
@Override
public Object fromJSON(Map<String, Object> map)
{
return new Custom();
}
public static class Custom
{
}
}
@Test
public void testContext()
{
AsyncJSON.Factory factory = new AsyncJSON.Factory()
{
@Override
public AsyncJSON newAsyncJSON()
{
return new AsyncJSON(this)
{
@Override
protected Map<String, Object> newObject(Context context)
{
if (context.depth() == 1)
{
return new CustomMap();
}
return super.newObject(context);
}
};
}
};
AsyncJSON parser = factory.newAsyncJSON();
String json = "[{" +
"\"channel\": \"/meta/handshake\"," +
"\"version\": \"1.0\"," +
"\"supportedConnectionTypes\": [\"long-polling\"]," +
"\"advice\": {\"timeout\": 0}" +
"}]";
assertTrue(parser.parse(UTF_8.encode(json)));
List<CustomMap> messages = parser.complete();
for (CustomMap message : messages)
{
@SuppressWarnings("unchecked")
Map<String, Object> advice = (Map<String, Object>)message.get("advice");
assertFalse(advice instanceof CustomMap);
}
}
public static class CustomMap extends HashMap<String, Object>
{
}
@Test
public void testCaching()
{
AsyncJSON.Factory factory = new AsyncJSON.Factory();
String foo = "foo";
factory.cache(foo);
AsyncJSON parser = factory.newAsyncJSON();
String json = "{\"foo\": [\"foo\", \"foo\"]}";
parser.parse(UTF_8.encode(json));
Map<String, Object> object = parser.complete();
Map.Entry<String, Object> entry = object.entrySet().iterator().next();
assertSame(foo, entry.getKey());
@SuppressWarnings("unchecked")
List<String> array = (List<String>)entry.getValue();
for (String item : array)
{
assertSame(foo, item);
}
}
@Test
public void testEncodedCaching()
{
AsyncJSON.Factory factory = new AsyncJSON.Factory();
assertFalse(factory.cache("yèck"));
String foo = "foo\\yuck";
assertTrue(factory.cache(foo));
AsyncJSON parser = factory.newAsyncJSON();
String json = "{\"foo\\\\yuck\": [\"foo\\\\yuck\", \"foo\\\\yuck\"]}";
parser.parse(UTF_8.encode(json));
Map<String, Object> object = parser.complete();
Map.Entry<String, Object> entry = object.entrySet().iterator().next();
assertSame(foo, entry.getKey());
@SuppressWarnings("unchecked")
List<String> array = (List<String>)entry.getValue();
for (String item : array)
{
assertSame(foo, item);
}
}
}

View File

@ -614,7 +614,7 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
boolean added = _trie.put(s, v); boolean added = _trie.put(s, v);
while (!added && _growby > 0) while (!added && _growby > 0)
{ {
ArrayTernaryTrie<V> bigger = new ArrayTernaryTrie<>(_trie._key.length + _growby); ArrayTernaryTrie<V> bigger = new ArrayTernaryTrie<>(_trie.isCaseInsensitive(), _trie._key.length + _growby);
for (Map.Entry<String, V> entry : _trie.entrySet()) for (Map.Entry<String, V> entry : _trie.entrySet())
{ {
bigger.put(entry.getKey(), entry.getValue()); bigger.put(entry.getKey(), entry.getValue());