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

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Ludovic Orban 2021-12-16 14:23:47 +01:00
commit 6793e7e985
12 changed files with 234 additions and 54 deletions

View File

@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory;
class AsyncContentProducer implements ContentProducer
{
private static final Logger LOG = LoggerFactory.getLogger(AsyncContentProducer.class);
private static final HttpInput.ErrorContent RECYCLED_ERROR_CONTENT = new HttpInput.ErrorContent(new IllegalStateException("ContentProducer has been recycled"));
private static final Throwable UNCONSUMED_CONTENT_EXCEPTION = new IOException("Unconsumed content")
{
@Override
@ -66,9 +67,30 @@ class AsyncContentProducer implements ContentProducer
assertLocked();
if (LOG.isDebugEnabled())
LOG.debug("recycling {}", this);
// Make sure that the content has been fully consumed before destroying the interceptor and also make sure
// that asking this instance for content between recycle and reopen will only produce error'ed content.
if (_rawContent == null)
_rawContent = RECYCLED_ERROR_CONTENT;
else if (!_rawContent.isSpecial())
throw new IllegalStateException("ContentProducer with unconsumed content cannot be recycled");
if (_transformedContent == null)
_transformedContent = RECYCLED_ERROR_CONTENT;
else if (!_transformedContent.isSpecial())
throw new IllegalStateException("ContentProducer with unconsumed content cannot be recycled");
if (_interceptor instanceof Destroyable)
((Destroyable)_interceptor).destroy();
_interceptor = null;
}
@Override
public void reopen()
{
assertLocked();
if (LOG.isDebugEnabled())
LOG.debug("reopening {}", this);
_rawContent = null;
_transformedContent = null;
_error = false;

View File

@ -46,6 +46,14 @@ class BlockingContentProducer implements ContentProducer
if (LOG.isDebugEnabled())
LOG.debug("recycling {}", this);
_asyncContentProducer.recycle();
}
@Override
public void reopen()
{
if (LOG.isDebugEnabled())
LOG.debug("reopening {}", this);
_asyncContentProducer.reopen();
_semaphore.drainPermits();
}

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.server;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.thread.AutoLock;
/**
@ -27,17 +28,24 @@ import org.eclipse.jetty.util.thread.AutoLock;
public interface ContentProducer
{
/**
* Lock this instance. The lock must be held before any method of this instance's
* method be called, and must be manually released afterward.
* Lock this instance. The lock must be held before any of this instance's
* method can be called, and must be released afterward.
* @return the lock that is guarding this instance.
*/
AutoLock lock();
/**
* Reset all internal state and clear any held resources.
* Clear the interceptor and call {@link Destroyable#destroy()} on it if it implements {@link Destroyable}.
* A recycled {@link ContentProducer} will only produce special content with a non-null error until
* {@link #reopen()} is called.
*/
void recycle();
/**
* Reset all internal state, making this is instance logically equivalent to a freshly allocated one.
*/
void reopen();
/**
* Fail all content currently available in this {@link ContentProducer} instance
* as well as in the underlying {@link HttpChannel}.

View File

@ -55,8 +55,12 @@ public class HttpInput extends ServletInputStream implements Runnable
public void recycle()
{
if (LOG.isDebugEnabled())
LOG.debug("recycle {}", this);
try (AutoLock lock = _contentProducer.lock())
{
if (LOG.isDebugEnabled())
LOG.debug("recycle {}", this);
_blockingContentProducer.recycle();
}
}
public void reopen()
@ -65,7 +69,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
if (LOG.isDebugEnabled())
LOG.debug("reopen {}", this);
_blockingContentProducer.recycle();
_blockingContentProducer.reopen();
_contentProducer = _blockingContentProducer;
_consumedEof = false;
_readListener = null;

View File

@ -18,6 +18,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Fast String Utilities.
@ -774,6 +775,47 @@ public class StringUtil
return true;
}
public static byte[] fromHexString(String s)
{
if (s.length() % 2 != 0)
throw new IllegalArgumentException(s);
byte[] array = new byte[s.length() / 2];
for (int i = 0; i < array.length; i++)
{
int b = Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
array[i] = (byte)(0xff & b);
}
return array;
}
public static String toHexString(byte b)
{
return toHexString(new byte[]{b}, 0, 1);
}
public static String toHexString(byte[] b)
{
return toHexString(Objects.requireNonNull(b, "ByteBuffer cannot be null"), 0, b.length);
}
public static String toHexString(byte[] b, int offset, int length)
{
StringBuilder buf = new StringBuilder();
for (int i = offset; i < offset + length; i++)
{
int bi = 0xff & b[i];
int c = '0' + (bi / 16) % 16;
if (c > '9')
c = 'A' + (c - '0' - 10);
buf.append((char)c);
c = '0' + bi % 16;
if (c > '9')
c = 'a' + (c - '0' - 10);
buf.append((char)c);
}
return buf.toString();
}
public static String printable(String name)
{
if (name == null)

View File

@ -400,6 +400,10 @@ public class TypeUtil
return value;
}
/**
* @deprecated use {@link StringUtil#fromHexString(String)} instead
*/
@Deprecated
public static byte[] parseBytes(String s, int base)
{
byte[] bytes = new byte[s.length() / 2];
@ -507,45 +511,40 @@ public class TypeUtil
toHex((int)value, buf);
}
/**
* @deprecated use {@link StringUtil#toHexString(byte)} instead
*/
@Deprecated
public static String toHexString(byte b)
{
return toHexString(new byte[]{b}, 0, 1);
return StringUtil.toHexString(b);
}
/**
* @deprecated use {@link StringUtil#toHexString(byte[])} instead
*/
@Deprecated
public static String toHexString(byte[] b)
{
return toHexString(b, 0, b.length);
return StringUtil.toHexString(b);
}
/**
* @deprecated use {@link StringUtil#toHexString(byte[], int, int)} instead
*/
@Deprecated
public static String toHexString(byte[] b, int offset, int length)
{
StringBuilder buf = new StringBuilder();
for (int i = offset; i < offset + length; i++)
{
int bi = 0xff & b[i];
int c = '0' + (bi / 16) % 16;
if (c > '9')
c = 'A' + (c - '0' - 10);
buf.append((char)c);
c = '0' + bi % 16;
if (c > '9')
c = 'a' + (c - '0' - 10);
buf.append((char)c);
}
return buf.toString();
return StringUtil.toHexString(b, offset, length);
}
/**
* @deprecated use {@link StringUtil#fromHexString(String)}
*/
@Deprecated
public static byte[] fromHexString(String s)
{
if (s.length() % 2 != 0)
throw new IllegalArgumentException(s);
byte[] array = new byte[s.length() / 2];
for (int i = 0; i < array.length; i++)
{
int b = Integer.parseInt(s.substring(i * 2, i * 2 + 2), 16);
array[i] = (byte)(0xff & b);
}
return array;
return StringUtil.fromHexString(s);
}
public static void dump(Class<?> c)

View File

@ -19,12 +19,9 @@ import java.security.MessageDigest;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Credentials. The Credential class represents an abstract mechanism for checking authentication credentials. A credential instance either represents a secret,
@ -39,10 +36,12 @@ import org.slf4j.LoggerFactory;
*/
public abstract class Credential implements Serializable
{
// NOTE: DO NOT INTRODUCE LOGGING TO THIS CLASS
private static final long serialVersionUID = -7760551052768181572L;
private static final Logger LOG = LoggerFactory.getLogger(Credential.class);
private static final List<CredentialProvider> CREDENTIAL_PROVIDERS = TypeUtil.serviceProviderStream(ServiceLoader.load(CredentialProvider.class))
.flatMap(p -> Stream.of(p.get()))
// Intentionally NOT using TypeUtil.serviceProviderStream
// as that introduces a Logger requirement that command line Password cannot use.
private static final List<CredentialProvider> CREDENTIAL_PROVIDERS = ServiceLoader.load(CredentialProvider.class).stream()
.map(ServiceLoader.Provider::get)
.collect(Collectors.toList());
/**
@ -154,8 +153,7 @@ public abstract class Credential implements Serializable
{
if (credentials instanceof char[])
credentials = new String((char[])credentials);
if (!(credentials instanceof String) && !(credentials instanceof Password))
LOG.warn("Can't check {} against CRYPT", credentials.getClass());
return stringEquals(_cooked, UnixCrypt.crypt(credentials.toString(), _cooked));
}
@ -189,7 +187,7 @@ public abstract class Credential implements Serializable
MD5(String digest)
{
digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest;
_digest = TypeUtil.parseBytes(digest, 16);
_digest = StringUtil.fromHexString(digest);
}
public byte[] getDigest()
@ -229,13 +227,12 @@ public abstract class Credential implements Serializable
}
else
{
LOG.warn("Can't check {} against MD5", credentials.getClass());
// Not a MD5 or Credential class
return false;
}
}
catch (Exception e)
{
LOG.warn("Failed message digest", e);
return false;
}
}
@ -248,6 +245,9 @@ public abstract class Credential implements Serializable
return false;
}
/**
* Used only by Command Line Password utility
*/
public static String digest(String password)
{
try
@ -263,7 +263,8 @@ public abstract class Credential implements Serializable
}
catch (Exception e)
{
LOG.warn("Unable to access MD5 message digest", e);
System.err.println("Unable to access MD5 message digest");
e.printStackTrace();
return null;
}
}
@ -273,11 +274,12 @@ public abstract class Credential implements Serializable
digest = __md.digest();
}
return __TYPE + TypeUtil.toString(digest, 16);
return __TYPE + StringUtil.toHexString(digest);
}
catch (Exception e)
{
LOG.warn("Message Digest failure", e);
System.err.println("Message Digest Failure");
e.printStackTrace();
return null;
}
}

View File

@ -17,9 +17,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Password utility class.
*
@ -47,8 +44,7 @@ import org.slf4j.LoggerFactory;
*/
public class Password extends Credential
{
private static final Logger LOG = LoggerFactory.getLogger(Password.class);
// NOTE: DO NOT INTRODUCE LOGGING TO THIS CLASS
private static final long serialVersionUID = 5062906681431569445L;
public static final String __OBFUSCATE = "OBF:";
@ -224,7 +220,9 @@ public class Password extends Credential
}
catch (IOException e)
{
LOG.warn("EXCEPTION", e);
// only seen with command line input style
System.err.println("ERROR: Bad/Invalid password.");
e.printStackTrace();
}
if (passwd == null || passwd.length() == 0)
passwd = promptDft;
@ -247,5 +245,6 @@ public class Password extends Credential
System.err.println(Credential.MD5.digest(p));
if (arg.length == 2)
System.err.println(Credential.Crypt.crypt(arg[0], pw.toString()));
System.exit(0);
}
}

View File

@ -26,8 +26,10 @@ import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.emptyArray;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
@ -266,4 +268,34 @@ public class StringUtilTest
assertThat(StringUtil.csvSplit("\"aaa\", \" b,\\\"\",\"\""), arrayContaining("aaa", " b,\"", ""));
}
@Test
public void testFromHexStringGood()
{
assertArrayEquals(new byte[]{0x12, 0x34, 0x56, 0x78, (byte)0x9A}, StringUtil.fromHexString("123456789A"));
}
@Test
public void testFromHexStringBad()
{
assertThrows(NumberFormatException.class, () -> StringUtil.fromHexString("Hello World "));
}
@Test
public void testToHexStringGood()
{
assertThat(StringUtil.toHexString(new byte[]{0x12, 0x34, 0x56, 0x78, (byte)0x9A}), is("123456789a"));
}
@Test
public void testToHexStringNull()
{
assertThrows(NullPointerException.class, () -> StringUtil.toHexString(null));
}
@Test
public void testToHexStringEmpty()
{
assertThat(StringUtil.toHexString(new byte[0]), is(""));
}
}

View File

@ -16,6 +16,8 @@ package org.eclipse.jetty.util.security;
import org.eclipse.jetty.util.security.Credential.Crypt;
import org.eclipse.jetty.util.security.Credential.MD5;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -99,4 +101,17 @@ public class CredentialTest
assertFalse(Credential.byteEquals("".getBytes(), "fooo".getBytes()));
assertTrue(Credential.byteEquals("".getBytes(), "".getBytes()));
}
@ParameterizedTest
@ValueSource(strings = {
"OBF:1v2j1uum1xtv1zej1zer1xtn1uvk1v1v",
"MD5:5f4dcc3b5aa765d61d8327deb882cf99",
"CRYPT:usjRS48E8ZADM"
})
public void testGetCredential(String encoded)
{
Credential credential = Credential.getCredential(encoded);
assertTrue(credential.check(Credential.getCredential("password")));
assertTrue(credential.check("password"));
}
}

View File

@ -13,9 +13,22 @@
package org.eclipse.jetty.util.security;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class PasswordTest
{
@ -44,4 +57,40 @@ public class PasswordTest
String obfuscate = Password.obfuscate(password);
assertEquals(password, Password.deobfuscate(obfuscate));
}
@Test
public void testCommandLineUsage() throws IOException, InterruptedException
{
ProcessBuilder passwordBuilder = new ProcessBuilder()
.directory(MavenTestingUtils.getTargetDir())
.command("java",
"-cp", MavenTestingUtils.getTargetPath("classes").toString(),
Password.class.getName(),
"user", "password")
.redirectErrorStream(true);
Process passwordProcess = passwordBuilder.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(passwordProcess.getInputStream())))
{
String output = reader.lines().collect(Collectors.joining(System.lineSeparator()));
if (passwordProcess.waitFor(5, TimeUnit.SECONDS))
{
int exitCode = passwordProcess.exitValue();
assertThat("Non-error exit code: " + output, exitCode, is(0));
assertThat("Output", output, not(containsString("Exception")));
assertThat("Output", output, allOf(
containsString("password"),
containsString("OBF:"),
containsString("MD5:"),
containsString("CRYPT:")
));
}
else
{
System.out.println(output);
passwordProcess.destroy();
fail("Process didn't exit properly (was forcibly destroyed)");
}
}
}
}

View File

@ -40,13 +40,13 @@
<felix.version>7.0.3</felix.version>
<findbugs.jsr305.version>3.0.2</findbugs.jsr305.version>
<google.errorprone.version>2.10.0</google.errorprone.version>
<grpc.version>1.42.1</grpc.version>
<grpc.version>1.43.0</grpc.version>
<gson.version>2.8.9</gson.version>
<guava.version>31.0.1-jre</guava.version>
<guice.version>5.0.1</guice.version>
<hamcrest.version>2.2</hamcrest.version>
<hawtio.version>2.14.2</hawtio.version>
<hazelcast.version>4.2.2</hazelcast.version>
<hazelcast.version>4.2.3</hazelcast.version>
<infinispan.version>11.0.11.Final</infinispan.version>
<infinispan.protostream.version>4.3.4.Final</infinispan.protostream.version>
<jackson-databind.version>2.13.0</jackson-databind.version>