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 class AsyncContentProducer implements ContentProducer
{ {
private static final Logger LOG = LoggerFactory.getLogger(AsyncContentProducer.class); 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") private static final Throwable UNCONSUMED_CONTENT_EXCEPTION = new IOException("Unconsumed content")
{ {
@Override @Override
@ -66,9 +67,30 @@ class AsyncContentProducer implements ContentProducer
assertLocked(); assertLocked();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("recycling {}", this); 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) if (_interceptor instanceof Destroyable)
((Destroyable)_interceptor).destroy(); ((Destroyable)_interceptor).destroy();
_interceptor = null; _interceptor = null;
}
@Override
public void reopen()
{
assertLocked();
if (LOG.isDebugEnabled())
LOG.debug("reopening {}", this);
_rawContent = null; _rawContent = null;
_transformedContent = null; _transformedContent = null;
_error = false; _error = false;

View File

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

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.AutoLock;
/** /**
@ -27,17 +28,24 @@ import org.eclipse.jetty.util.thread.AutoLock;
public interface ContentProducer public interface ContentProducer
{ {
/** /**
* Lock this instance. The lock must be held before any method of this instance's * Lock this instance. The lock must be held before any of this instance's
* method be called, and must be manually released afterward. * method can be called, and must be released afterward.
* @return the lock that is guarding this instance. * @return the lock that is guarding this instance.
*/ */
AutoLock lock(); 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(); 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 * Fail all content currently available in this {@link ContentProducer} instance
* as well as in the underlying {@link HttpChannel}. * as well as in the underlying {@link HttpChannel}.

View File

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

View File

@ -18,6 +18,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Fast String Utilities. * Fast String Utilities.
@ -774,6 +775,47 @@ public class StringUtil
return true; 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) public static String printable(String name)
{ {
if (name == null) if (name == null)

View File

@ -400,6 +400,10 @@ public class TypeUtil
return value; return value;
} }
/**
* @deprecated use {@link StringUtil#fromHexString(String)} instead
*/
@Deprecated
public static byte[] parseBytes(String s, int base) public static byte[] parseBytes(String s, int base)
{ {
byte[] bytes = new byte[s.length() / 2]; byte[] bytes = new byte[s.length() / 2];
@ -507,45 +511,40 @@ public class TypeUtil
toHex((int)value, buf); toHex((int)value, buf);
} }
/**
* @deprecated use {@link StringUtil#toHexString(byte)} instead
*/
@Deprecated
public static String toHexString(byte b) 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) 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) public static String toHexString(byte[] b, int offset, int length)
{ {
StringBuilder buf = new StringBuilder(); return StringUtil.toHexString(b, offset, length);
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();
} }
/**
* @deprecated use {@link StringUtil#fromHexString(String)}
*/
@Deprecated
public static byte[] fromHexString(String s) public static byte[] fromHexString(String s)
{ {
if (s.length() % 2 != 0) return StringUtil.fromHexString(s);
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 void dump(Class<?> c) public static void dump(Class<?> c)

View File

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

View File

@ -17,9 +17,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Locale; import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Password utility class. * Password utility class.
* *
@ -47,8 +44,7 @@ import org.slf4j.LoggerFactory;
*/ */
public class Password extends Credential 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; private static final long serialVersionUID = 5062906681431569445L;
public static final String __OBFUSCATE = "OBF:"; public static final String __OBFUSCATE = "OBF:";
@ -224,7 +220,9 @@ public class Password extends Credential
} }
catch (IOException e) 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) if (passwd == null || passwd.length() == 0)
passwd = promptDft; passwd = promptDft;
@ -247,5 +245,6 @@ public class Password extends Credential
System.err.println(Credential.MD5.digest(p)); System.err.println(Credential.MD5.digest(p));
if (arg.length == 2) if (arg.length == 2)
System.err.println(Credential.Crypt.crypt(arg[0], pw.toString())); 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.emptyArray;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck // @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
@ -266,4 +268,34 @@ public class StringUtilTest
assertThat(StringUtil.csvSplit("\"aaa\", \" b,\\\"\",\"\""), arrayContaining("aaa", " b,\"", "")); 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.Crypt;
import org.eclipse.jetty.util.security.Credential.MD5; import org.eclipse.jetty.util.security.Credential.MD5;
import org.junit.jupiter.api.Test; 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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -99,4 +101,17 @@ public class CredentialTest
assertFalse(Credential.byteEquals("".getBytes(), "fooo".getBytes())); assertFalse(Credential.byteEquals("".getBytes(), "fooo".getBytes()));
assertTrue(Credential.byteEquals("".getBytes(), "".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; 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 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.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class PasswordTest public class PasswordTest
{ {
@ -44,4 +57,40 @@ public class PasswordTest
String obfuscate = Password.obfuscate(password); String obfuscate = Password.obfuscate(password);
assertEquals(password, Password.deobfuscate(obfuscate)); 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> <felix.version>7.0.3</felix.version>
<findbugs.jsr305.version>3.0.2</findbugs.jsr305.version> <findbugs.jsr305.version>3.0.2</findbugs.jsr305.version>
<google.errorprone.version>2.10.0</google.errorprone.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> <gson.version>2.8.9</gson.version>
<guava.version>31.0.1-jre</guava.version> <guava.version>31.0.1-jre</guava.version>
<guice.version>5.0.1</guice.version> <guice.version>5.0.1</guice.version>
<hamcrest.version>2.2</hamcrest.version> <hamcrest.version>2.2</hamcrest.version>
<hawtio.version>2.14.2</hawtio.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.version>11.0.11.Final</infinispan.version>
<infinispan.protostream.version>4.3.4.Final</infinispan.protostream.version> <infinispan.protostream.version>4.3.4.Final</infinispan.protostream.version>
<jackson-databind.version>2.13.0</jackson-databind.version> <jackson-databind.version>2.13.0</jackson-databind.version>