mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
feat: support ws proxy
This commit is contained in:
@@ -77,6 +77,10 @@ public class MemShellGenerateRequest {
|
|||||||
.shellClassBase64(shellToolConfig.getShellClassBase64())
|
.shellClassBase64(shellToolConfig.getShellClassBase64())
|
||||||
.shellClassName(shellToolConfig.getShellClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.build();
|
.build();
|
||||||
|
case Proxy -> ProxyConfig.builder()
|
||||||
|
.headerName(shellToolConfig.getHeaderName())
|
||||||
|
.headerValue(shellToolConfig.getHeaderValue())
|
||||||
|
.shellClassName(shellToolConfig.shellClassName).build();
|
||||||
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
|
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.*;
|
|||||||
import com.reajason.javaweb.memshell.shelltool.neoreg.*;
|
import com.reajason.javaweb.memshell.shelltool.neoreg.*;
|
||||||
import com.reajason.javaweb.memshell.shelltool.suo5.*;
|
import com.reajason.javaweb.memshell.shelltool.suo5.*;
|
||||||
import com.reajason.javaweb.memshell.shelltool.suo5v2.*;
|
import com.reajason.javaweb.memshell.shelltool.suo5v2.*;
|
||||||
|
import com.reajason.javaweb.memshell.shelltool.wsproxy.ProxyWebSocket;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -239,6 +240,13 @@ public class ServerFactory {
|
|||||||
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
|
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
|
||||||
.addShellClass(ACTION, NeoreGeorgStruct2Action.class)
|
.addShellClass(ACTION, NeoreGeorgStruct2Action.class)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
|
addToolMapping(ShellTool.Proxy, ToolMapping.builder()
|
||||||
|
.addShellClass(WEBSOCKET, ProxyWebSocket.class)
|
||||||
|
.addShellClass(JAKARTA_WEBSOCKET, ProxyWebSocket.class)
|
||||||
|
.addShellClass(BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
|
||||||
|
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void register(String serverName, Supplier<AbstractServer> shellSupplier) {
|
public static void register(String serverName, Supplier<AbstractServer> shellSupplier) {
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ public class ShellTool {
|
|||||||
public static final String Suo5v2 = "Suo5v2";
|
public static final String Suo5v2 = "Suo5v2";
|
||||||
public static final String AntSword = "AntSword";
|
public static final String AntSword = "AntSword";
|
||||||
public static final String NeoreGeorg = "NeoreGeorg";
|
public static final String NeoreGeorg = "NeoreGeorg";
|
||||||
|
public static final String Proxy = "Proxy";
|
||||||
public static final String Custom = "Custom";
|
public static final String Custom = "Custom";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public class ShellToolFactory {
|
|||||||
register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class);
|
register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class);
|
||||||
register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class);
|
register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class);
|
||||||
register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class);
|
register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class);
|
||||||
|
register(ShellTool.Proxy, ProxyGenerator.class, ProxyConfig.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void register(String shellToolName, Class<? extends ShellGenerator> generatorClass, Class<? extends ShellToolConfig> configClass) {
|
public static void register(String shellToolName, Class<? extends ShellGenerator> generatorClass, Class<? extends ShellToolConfig> configClass) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.reajason.javaweb.memshell.config;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.utils.CommonUtil;
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.SuperBuilder;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@SuperBuilder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class ProxyConfig extends ShellToolConfig {
|
||||||
|
@Builder.Default
|
||||||
|
private String headerName = "User-Agent";
|
||||||
|
@Builder.Default
|
||||||
|
private String headerValue = CommonUtil.getRandomString(8);
|
||||||
|
|
||||||
|
public static abstract class ProxyConfigBuilder<C extends ProxyConfig, B extends ProxyConfig.ProxyConfigBuilder<C, B>>
|
||||||
|
extends ShellToolConfig.ShellToolConfigBuilder<C, B> {
|
||||||
|
|
||||||
|
public B headerName(final String headerName) {
|
||||||
|
if (StringUtils.isNotBlank(headerName)) {
|
||||||
|
this.headerName$value = headerName;
|
||||||
|
headerName$set = true;
|
||||||
|
}
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public B headerValue(final String headerValue) {
|
||||||
|
if (StringUtils.isNotBlank(headerValue)) {
|
||||||
|
this.headerValue$value = headerValue;
|
||||||
|
headerValue$set = true;
|
||||||
|
}
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.reajason.javaweb.memshell.generator;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.memshell.config.ProxyConfig;
|
||||||
|
import com.reajason.javaweb.memshell.config.ShellConfig;
|
||||||
|
import net.bytebuddy.ByteBuddy;
|
||||||
|
import net.bytebuddy.dynamic.DynamicType;
|
||||||
|
|
||||||
|
public class ProxyGenerator extends ByteBuddyShellGenerator<ProxyConfig> {
|
||||||
|
public ProxyGenerator(ShellConfig shellConfig, ProxyConfig shellToolConfig) {
|
||||||
|
super(shellConfig, shellToolConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DynamicType.Builder<?> getBuilder() {
|
||||||
|
return new ByteBuddy().redefine(shellToolConfig.getShellClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-4
@@ -5,10 +5,7 @@ import com.reajason.javaweb.GenerationException;
|
|||||||
import com.reajason.javaweb.Server;
|
import com.reajason.javaweb.Server;
|
||||||
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
|
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
|
||||||
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
|
||||||
import com.reajason.javaweb.memshell.config.CommandConfig;
|
import com.reajason.javaweb.memshell.config.*;
|
||||||
import com.reajason.javaweb.memshell.config.GodzillaConfig;
|
|
||||||
import com.reajason.javaweb.memshell.config.ShellConfig;
|
|
||||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
|
||||||
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
|
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
|
||||||
import com.reajason.javaweb.utils.CommonUtil;
|
import com.reajason.javaweb.utils.CommonUtil;
|
||||||
import net.bytebuddy.ByteBuddy;
|
import net.bytebuddy.ByteBuddy;
|
||||||
@@ -50,6 +47,8 @@ public class WebSocketByPassHelperGenerator {
|
|||||||
return Pair.of(((CommandConfig) shellToolConfig).getHeaderName(), ((CommandConfig) shellToolConfig).getHeaderValue());
|
return Pair.of(((CommandConfig) shellToolConfig).getHeaderName(), ((CommandConfig) shellToolConfig).getHeaderValue());
|
||||||
} else if (shellToolConfig instanceof GodzillaConfig) {
|
} else if (shellToolConfig instanceof GodzillaConfig) {
|
||||||
return Pair.of(((GodzillaConfig) shellToolConfig).getHeaderName(), ((GodzillaConfig) shellToolConfig).getHeaderValue());
|
return Pair.of(((GodzillaConfig) shellToolConfig).getHeaderName(), ((GodzillaConfig) shellToolConfig).getHeaderValue());
|
||||||
|
} else if (shellToolConfig instanceof ProxyConfig) {
|
||||||
|
return Pair.of(((ProxyConfig) shellToolConfig).getHeaderName(), ((ProxyConfig) shellToolConfig).getHeaderValue());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<%--
|
||||||
|
Created by IntelliJ IDEA.
|
||||||
|
User: ReaJason
|
||||||
|
Date: 2026/1/16
|
||||||
|
Time: 23:12
|
||||||
|
To change this template use File | Settings | File Templates.
|
||||||
|
--%>
|
||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>$Title$</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
$END$
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,16 +1,5 @@
|
|||||||
import {
|
import { ArrowUpRightIcon, InfoIcon, ServerIcon } from "lucide-react";
|
||||||
ArrowUpRightIcon,
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
AxeIcon,
|
|
||||||
CommandIcon,
|
|
||||||
InfoIcon,
|
|
||||||
NetworkIcon,
|
|
||||||
ServerIcon,
|
|
||||||
ShieldOffIcon,
|
|
||||||
SwordIcon,
|
|
||||||
WaypointsIcon,
|
|
||||||
ZapIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { type JSX, useCallback, useRef, useState } from "react";
|
|
||||||
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
|
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab";
|
import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab";
|
||||||
@@ -51,17 +40,7 @@ import type { MemShellFormSchema } from "@/types/schema";
|
|||||||
import { Spinner } from "../ui/spinner";
|
import { Spinner } from "../ui/spinner";
|
||||||
import { JREVersionFormField } from "./jreversion-field";
|
import { JREVersionFormField } from "./jreversion-field";
|
||||||
import { ServerVersionFormField } from "./serverversion-field";
|
import { ServerVersionFormField } from "./serverversion-field";
|
||||||
|
import { ProxyTabContent } from "./tabs/proxy-tab";
|
||||||
const shellToolIcons: Record<ShellToolType, JSX.Element> = {
|
|
||||||
[ShellToolType.Behinder]: <ShieldOffIcon className="h-4 w-4" />,
|
|
||||||
[ShellToolType.Godzilla]: <AxeIcon className="h-4 w-4" />,
|
|
||||||
[ShellToolType.Command]: <CommandIcon className="h-4 w-4" />,
|
|
||||||
[ShellToolType.AntSword]: <SwordIcon className="h-4 w-4" />,
|
|
||||||
[ShellToolType.Suo5]: <WaypointsIcon className="h-4 w-4" />,
|
|
||||||
[ShellToolType.Suo5v2]: <WaypointsIcon className="h-4 w-4" />,
|
|
||||||
[ShellToolType.NeoreGeorg]: <NetworkIcon className="h-4 w-4" />,
|
|
||||||
[ShellToolType.Custom]: <ZapIcon className="h-4 w-4" />,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function MainConfigCard({
|
export default function MainConfigCard({
|
||||||
mainConfig,
|
mainConfig,
|
||||||
@@ -74,62 +53,108 @@ export default function MainConfigCard({
|
|||||||
}>) {
|
}>) {
|
||||||
const { t } = useTranslation(["common", "memshell"]);
|
const { t } = useTranslation(["common", "memshell"]);
|
||||||
|
|
||||||
const [shellToolMap, setShellToolMap] = useState<{
|
const server = useWatch({
|
||||||
[toolName: string]: string[];
|
control: form.control,
|
||||||
}>();
|
name: "server",
|
||||||
const [shellTools, setShellTools] = useState<ShellToolType[]>([]);
|
});
|
||||||
const [shellTypes, setShellTypes] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const shellTool = useWatch({
|
const shellTool = useWatch({
|
||||||
control: form.control,
|
control: form.control,
|
||||||
name: "shellTool",
|
name: "shellTool",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleServerChange = useCallback(
|
const serverToolMap = useMemo(() => {
|
||||||
(value: string) => {
|
if (!mainConfig || !server) {
|
||||||
if (mainConfig) {
|
return undefined;
|
||||||
const newShellToolMap = mainConfig[value];
|
}
|
||||||
setShellToolMap(newShellToolMap);
|
return mainConfig[server];
|
||||||
|
}, [mainConfig, server]);
|
||||||
|
|
||||||
const newShellTools = Object.keys(newShellToolMap);
|
const serverOptions = useMemo(() => Object.keys(servers ?? {}), [servers]);
|
||||||
setShellTools([
|
|
||||||
...newShellTools.map((tool) => tool as ShellToolType),
|
|
||||||
ShellToolType.Custom,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const currentShellTool = form.getValues("shellTool");
|
const shellTools = useMemo(() => {
|
||||||
|
if (!serverToolMap) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const tools = Object.keys(serverToolMap).map(
|
||||||
|
(tool) => tool as ShellToolType,
|
||||||
|
);
|
||||||
|
return Array.from(new Set([...tools, ShellToolType.Custom]));
|
||||||
|
}, [serverToolMap]);
|
||||||
|
|
||||||
const firstTool = newShellTools[0];
|
const customShellTypes = useMemo(() => {
|
||||||
let currentShellTypes = null;
|
if (!server) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return servers?.[server] ?? [];
|
||||||
|
}, [server, servers]);
|
||||||
|
|
||||||
if (!newShellToolMap[currentShellTool]) {
|
const shellTypes = useMemo(() => {
|
||||||
form.setValue("shellTool", firstTool);
|
if (!serverToolMap || !server) {
|
||||||
currentShellTypes = newShellToolMap[firstTool];
|
return [];
|
||||||
} else {
|
}
|
||||||
currentShellTypes = newShellToolMap[currentShellTool];
|
if (shellTool === ShellToolType.Custom) {
|
||||||
|
return customShellTypes;
|
||||||
|
}
|
||||||
|
return serverToolMap[shellTool] ?? [];
|
||||||
|
}, [customShellTypes, server, serverToolMap, shellTool]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mainConfig || !server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toolMap = mainConfig[server];
|
||||||
|
if (!toolMap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toolKeys = Object.keys(toolMap);
|
||||||
|
if (toolKeys.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentShellTool = form.getValues("shellTool") as ShellToolType;
|
||||||
|
const nextShellTool = toolMap[currentShellTool]
|
||||||
|
? currentShellTool
|
||||||
|
: (toolKeys[0] as ShellToolType);
|
||||||
|
|
||||||
|
if (nextShellTool !== currentShellTool) {
|
||||||
|
form.setValue("shellTool", nextShellTool);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextShellTool !== ShellToolType.Custom) {
|
||||||
|
const nextShellTypes = toolMap[nextShellTool] ?? [];
|
||||||
|
if (nextShellTypes.length > 0) {
|
||||||
|
const currentShellType = form.getValues("shellType");
|
||||||
|
if (currentShellType !== nextShellTypes[0]) {
|
||||||
|
form.setValue("shellType", nextShellTypes[0]);
|
||||||
}
|
}
|
||||||
setShellTypes(currentShellTypes);
|
|
||||||
|
|
||||||
if (currentShellTypes && currentShellTypes.length > 0) {
|
|
||||||
form.setValue("shellType", currentShellTypes[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(value === "SpringWebFlux" || value === "XXLJOB") &&
|
|
||||||
Number.parseInt(form.getValues("targetJdkVersion") as string, 10) < 52
|
|
||||||
) {
|
|
||||||
form.setValue("targetJdkVersion", "52");
|
|
||||||
} else {
|
|
||||||
form.setValue("targetJdkVersion", "50");
|
|
||||||
}
|
|
||||||
|
|
||||||
form.resetField("serverVersion");
|
|
||||||
form.resetField("byPassJavaModule");
|
|
||||||
form.resetField("urlPattern");
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
[form, mainConfig],
|
|
||||||
);
|
const currentTargetJdk = form.getValues("targetJdkVersion") as string;
|
||||||
|
const currentJdkVersion = Number.parseInt(currentTargetJdk, 10);
|
||||||
|
const shouldRaiseJdkVersion =
|
||||||
|
(server === "SpringWebFlux" || server === "XXLJOB") &&
|
||||||
|
currentJdkVersion < 52;
|
||||||
|
const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50";
|
||||||
|
if (currentTargetJdk !== nextJdkVersion) {
|
||||||
|
form.setValue("targetJdkVersion", nextJdkVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
form.resetField("serverVersion");
|
||||||
|
form.resetField("byPassJavaModule");
|
||||||
|
form.resetField("urlPattern");
|
||||||
|
}, [form, mainConfig, server]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shellTool !== ShellToolType.Custom || customShellTypes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentShellType = form.getValues("shellType");
|
||||||
|
if (currentShellType !== customShellTypes[0]) {
|
||||||
|
form.setValue("shellType", customShellTypes[0]);
|
||||||
|
}
|
||||||
|
}, [customShellTypes, form, shellTool]);
|
||||||
|
|
||||||
const handleShellToolChange = useCallback(
|
const handleShellToolChange = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
@@ -172,16 +197,15 @@ export default function MainConfigCard({
|
|||||||
form.resetField("shellClassBase64");
|
form.resetField("shellClassBase64");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shellToolMap) {
|
if (serverToolMap) {
|
||||||
let currentShellTypes = null;
|
let currentShellTypes = null;
|
||||||
if (value === ShellToolType.Custom) {
|
if (value === ShellToolType.Custom) {
|
||||||
currentShellTypes = servers?.[form.getValues("server")] as string[];
|
currentShellTypes = customShellTypes;
|
||||||
} else {
|
} else {
|
||||||
currentShellTypes = shellToolMap[value];
|
currentShellTypes = serverToolMap[value];
|
||||||
}
|
}
|
||||||
setShellTypes(currentShellTypes);
|
|
||||||
|
|
||||||
// 直接设置 shellType 而不是依赖 useEffect
|
// Set shellType directly instead of relying on useEffect.
|
||||||
if (currentShellTypes && currentShellTypes.length > 0) {
|
if (currentShellTypes && currentShellTypes.length > 0) {
|
||||||
form.setValue("shellType", currentShellTypes[0]);
|
form.setValue("shellType", currentShellTypes[0]);
|
||||||
}
|
}
|
||||||
@@ -207,18 +231,9 @@ export default function MainConfigCard({
|
|||||||
}
|
}
|
||||||
form.setValue("shellTool", value);
|
form.setValue("shellTool", value);
|
||||||
},
|
},
|
||||||
[form, servers, shellToolMap],
|
[customShellTypes, form, serverToolMap],
|
||||||
);
|
);
|
||||||
|
|
||||||
const initializedRef = useRef(false);
|
|
||||||
if (!initializedRef.current && mainConfig) {
|
|
||||||
const initialServer = form.getValues("server");
|
|
||||||
if (initialServer && mainConfig[initialServer]) {
|
|
||||||
handleServerChange(initialServer);
|
|
||||||
initializedRef.current = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -249,10 +264,7 @@ export default function MainConfigCard({
|
|||||||
{t("common:server")}
|
{t("common:server")}
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(v) => {
|
onValueChange={field.onChange}
|
||||||
field.onChange(v);
|
|
||||||
handleServerChange(v as string);
|
|
||||||
}}
|
|
||||||
value={field.value}
|
value={field.value}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="server">
|
<SelectTrigger id="server">
|
||||||
@@ -261,13 +273,14 @@ export default function MainConfigCard({
|
|||||||
/>
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{Object.keys(servers ?? {}).map(
|
{serverOptions.map((serverOption) => (
|
||||||
(server: string) => (
|
<SelectItem
|
||||||
<SelectItem key={server} value={server}>
|
key={serverOption}
|
||||||
{server}
|
value={serverOption}
|
||||||
</SelectItem>
|
>
|
||||||
),
|
{serverOption}
|
||||||
)}
|
</SelectItem>
|
||||||
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FieldDescription className="flex items-center">
|
<FieldDescription className="flex items-center">
|
||||||
@@ -312,10 +325,7 @@ export default function MainConfigCard({
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{shellTools.map((tool) => (
|
{shellTools.map((tool) => (
|
||||||
<SelectItem key={tool} value={tool}>
|
<SelectItem key={tool} value={tool}>
|
||||||
<span className="flex items-center gap-2">
|
{tool}
|
||||||
{shellToolIcons[tool]}
|
|
||||||
{tool}
|
|
||||||
</span>
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -484,6 +494,7 @@ export default function MainConfigCard({
|
|||||||
/>
|
/>
|
||||||
<NeoRegTabContent form={form} shellTypes={shellTypes} />
|
<NeoRegTabContent form={form} shellTypes={shellTypes} />
|
||||||
<CustomTabContent form={form} shellTypes={shellTypes} />
|
<CustomTabContent form={form} shellTypes={shellTypes} />
|
||||||
|
<ProxyTabContent form={form} shellTypes={shellTypes} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
type GodzillaShellToolConfig,
|
type GodzillaShellToolConfig,
|
||||||
type MemShellResult,
|
type MemShellResult,
|
||||||
type NeoreGeorgShellToolConfig,
|
type NeoreGeorgShellToolConfig,
|
||||||
|
type ProxyShellToolConfig,
|
||||||
ShellToolType,
|
ShellToolType,
|
||||||
type Suo5ShellToolConfig,
|
type Suo5ShellToolConfig,
|
||||||
} from "@/types/memshell";
|
} from "@/types/memshell";
|
||||||
@@ -163,6 +164,13 @@ export function BasicInfo({
|
|||||||
value={`${(generateResult?.shellToolConfig as Suo5ShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as Suo5ShellToolConfig).headerValue}`}
|
value={`${(generateResult?.shellToolConfig as Suo5ShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as Suo5ShellToolConfig).headerValue}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{generateResult?.shellConfig.shellTool === ShellToolType.Proxy && (
|
||||||
|
<CopyableField
|
||||||
|
label={t("shellToolConfig.httpHeader")}
|
||||||
|
text={`${(generateResult?.shellToolConfig as ProxyShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as ProxyShellToolConfig).headerValue}`}
|
||||||
|
value={`${(generateResult?.shellToolConfig as ProxyShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as ProxyShellToolConfig).headerValue}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{generateResult?.shellConfig.shellTool === ShellToolType.AntSword && (
|
{generateResult?.shellConfig.shellTool === ShellToolType.AntSword && (
|
||||||
<>
|
<>
|
||||||
<CopyableField
|
<CopyableField
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function FeedbackAlert() {
|
|||||||
const { t } = useTranslation("memshell");
|
const { t } = useTranslation("memshell");
|
||||||
return (
|
return (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger>
|
||||||
<Button variant="outline" type="button">
|
<Button variant="outline" type="button">
|
||||||
<CircleHelpIcon /> {t("shellNotWork.title")}
|
<CircleHelpIcon /> {t("shellNotWork.title")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DownloadIcon } from "lucide-react";
|
import { DownloadIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CodeViewer from "@/components/code-viewer";
|
import CodeViewer from "@/components/code-viewer";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -25,30 +25,36 @@ export function MultiPackResult({
|
|||||||
}>) {
|
}>) {
|
||||||
const showCode = packMethod === "JSP";
|
const showCode = packMethod === "JSP";
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const packMethods = Object.keys(allPackResults ?? {});
|
const packResults = allPackResults as Record<string, string> | undefined;
|
||||||
|
const packMethods = useMemo(
|
||||||
const [selectedMethod, setSelectedMethod] = useState(packMethods[0]);
|
() => Object.keys(packResults ?? {}),
|
||||||
const [packResult, setPackResult] = useState(
|
[packResults],
|
||||||
allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const [selectedMethod, setSelectedMethod] = useState(
|
||||||
const newPackMethods = Object.keys(allPackResults ?? {});
|
() => packMethods[0] ?? "",
|
||||||
if (!newPackMethods.includes(selectedMethod)) {
|
);
|
||||||
const newSelectedMethod = newPackMethods[0];
|
|
||||||
setSelectedMethod(newSelectedMethod);
|
|
||||||
setPackResult(
|
|
||||||
allPackResults?.[newSelectedMethod as keyof typeof allPackResults] ??
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setPackResult(
|
|
||||||
allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [allPackResults, selectedMethod]);
|
|
||||||
|
|
||||||
const handleDownload = () => {
|
const packResult = useMemo(() => {
|
||||||
|
if (!selectedMethod) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return packResults?.[selectedMethod] ?? "";
|
||||||
|
}, [packResults, selectedMethod]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (packMethods.length === 0) {
|
||||||
|
if (selectedMethod !== "") {
|
||||||
|
setSelectedMethod("");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!packMethods.includes(selectedMethod)) {
|
||||||
|
setSelectedMethod(packMethods[0]);
|
||||||
|
}
|
||||||
|
}, [packMethods, selectedMethod]);
|
||||||
|
|
||||||
|
const handleDownload = useCallback(() => {
|
||||||
const fileName =
|
const fileName =
|
||||||
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
|
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
|
||||||
if (packMethod === "JSP") {
|
if (packMethod === "JSP") {
|
||||||
@@ -64,13 +70,17 @@ export function MultiPackResult({
|
|||||||
});
|
});
|
||||||
return downloadContent(content, fileName, ".data");
|
return downloadContent(content, fileName, ".data");
|
||||||
} else if (packMethod === "Base64") {
|
} else if (packMethod === "Base64") {
|
||||||
const base64Content =
|
const base64Content = packResults?.[packMethods[0]] ?? "";
|
||||||
allPackResults?.[
|
|
||||||
Object.keys(allPackResults)[0] as keyof typeof allPackResults
|
|
||||||
] ?? "";
|
|
||||||
return downloadBytes(base64Content, shellClassName);
|
return downloadBytes(base64Content, shellClassName);
|
||||||
}
|
}
|
||||||
};
|
}, [
|
||||||
|
packMethod,
|
||||||
|
packMethods,
|
||||||
|
packResult,
|
||||||
|
packResults,
|
||||||
|
selectedMethod,
|
||||||
|
shellClassName,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeViewer
|
<CodeViewer
|
||||||
@@ -80,9 +90,6 @@ export function MultiPackResult({
|
|||||||
<Select
|
<Select
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setSelectedMethod(value as string);
|
setSelectedMethod(value as string);
|
||||||
setPackResult(
|
|
||||||
allPackResults?.[value as keyof typeof allPackResults] ?? "",
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
value={selectedMethod}
|
value={selectedMethod}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Field, FieldLabel } from "@/components/ui/field";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
|
import type { MemShellFormSchema } from "@/types/schema";
|
||||||
|
import { OptionalClassFormField } from "./classname-field";
|
||||||
|
import { ShellTypeFormField } from "./shelltype-field";
|
||||||
|
|
||||||
|
export function ProxyTabContent({
|
||||||
|
form,
|
||||||
|
shellTypes,
|
||||||
|
}: Readonly<{
|
||||||
|
form: UseFormReturn<MemShellFormSchema>;
|
||||||
|
shellTypes: Array<string>;
|
||||||
|
}>) {
|
||||||
|
const shellType = useWatch({
|
||||||
|
name: "shellType",
|
||||||
|
control: form.control,
|
||||||
|
});
|
||||||
|
const { t } = useTranslation(["memshell", "common"]);
|
||||||
|
return (
|
||||||
|
<TabsContent value="Proxy">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="space-y-2 mt-4">
|
||||||
|
<ShellTypeFormField form={form} shellTypes={shellTypes} />
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-1 md:grid-cols-2 gap-2"
|
||||||
|
hidden={
|
||||||
|
shellType !== "BypassNginxWebSocket" &&
|
||||||
|
shellType !== "BypassNginxJakartaWebSocket"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={form.control}
|
||||||
|
name="headerName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Field className="gap-1">
|
||||||
|
<FieldLabel>{t("common:headerName")}</FieldLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t("common:placeholders.input")}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={form.control}
|
||||||
|
name="headerValue"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Field className="gap-1">
|
||||||
|
<FieldLabel>
|
||||||
|
{t("common:headerValue")} {t("common:optional")}
|
||||||
|
</FieldLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t("common:placeholders.input")}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<OptionalClassFormField form={form} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ export function BasicInfo({
|
|||||||
console.log(generateResult);
|
console.log(generateResult);
|
||||||
const isBodyContent =
|
const isBodyContent =
|
||||||
generateResult?.probeConfig.probeMethod === "ResponseBody";
|
generateResult?.probeConfig.probeMethod === "ResponseBody";
|
||||||
|
const isFilterContent = generateResult?.probeConfig.probeContent === "Filter";
|
||||||
const isBodyCommand =
|
const isBodyCommand =
|
||||||
isBodyContent && generateResult?.probeConfig.probeContent === "Command";
|
isBodyContent && generateResult?.probeConfig.probeContent === "Command";
|
||||||
return (
|
return (
|
||||||
@@ -27,7 +28,7 @@ export function BasicInfo({
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-1 gap-2">
|
<div className="grid grid-cols-1 gap-2">
|
||||||
{isBodyContent && (
|
{!isFilterContent && isBodyContent && (
|
||||||
<CopyableField
|
<CopyableField
|
||||||
label={t("common:paramName")}
|
label={t("common:paramName")}
|
||||||
value={
|
value={
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
FieldLabel,
|
FieldLabel,
|
||||||
} from "@/components/ui/field";
|
} from "@/components/ui/field";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -19,7 +18,7 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { SwitchField } from "@/components/ui/switch-field";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -28,6 +27,11 @@ import {
|
|||||||
import type { ServerConfig } from "@/types/memshell";
|
import type { ServerConfig } from "@/types/memshell";
|
||||||
import type { ProbeShellFormSchema } from "@/types/schema";
|
import type { ProbeShellFormSchema } from "@/types/schema";
|
||||||
|
|
||||||
|
// Hoisted static JSX to avoid recreation on each render (rendering-hoist-jsx)
|
||||||
|
const infoIcon = (
|
||||||
|
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||||
|
);
|
||||||
|
|
||||||
const PROBE_OPTIONS = [
|
const PROBE_OPTIONS = [
|
||||||
{ value: "Server" as const, label: "server" },
|
{ value: "Server" as const, label: "server" },
|
||||||
{ value: "JDK" as const, label: "jdk" },
|
{ value: "JDK" as const, label: "jdk" },
|
||||||
@@ -251,117 +255,35 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
|
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
|
||||||
<Controller
|
<SwitchField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="debug"
|
name="debug"
|
||||||
render={({ field }) => (
|
label={t("common:debug")}
|
||||||
<div className="flex items-center gap-2">
|
description={t("common:debug.description")}
|
||||||
<Switch
|
|
||||||
id="debug"
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="debug">{t("common:debug")}</Label>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("common:debug.description")}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Controller
|
<SwitchField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="byPassJavaModule"
|
name="byPassJavaModule"
|
||||||
render={({ field }) => (
|
label={t("common:byPassJavaModule")}
|
||||||
<div className="flex items-center gap-2">
|
description={t("common:byPassJavaModule.description")}
|
||||||
<Switch
|
|
||||||
id="bypass"
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="bypass">{t("common:byPassJavaModule")}</Label>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("common:byPassJavaModule.description")}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Controller
|
<SwitchField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="lambdaSuffix"
|
name="lambdaSuffix"
|
||||||
render={({ field }) => (
|
label={t("common:lambdaSuffix")}
|
||||||
<div className="flex items-center gap-2">
|
description={t("common:lambdaSuffix.description")}
|
||||||
<Switch
|
|
||||||
id="lambdaSuffix"
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="lambdaSuffix">{t("common:lambdaSuffix")}</Label>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("common:lambdaSuffix.description")}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Controller
|
<SwitchField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="shrink"
|
name="shrink"
|
||||||
render={({ field }) => (
|
label={t("common:shrink")}
|
||||||
<div className="flex items-center gap-2">
|
description={t("common:shrink.description")}
|
||||||
<Switch
|
|
||||||
id="shrink"
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="shrink">{t("common:shrink")}</Label>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("common:shrink.description")}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Controller
|
<SwitchField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="staticInitialize"
|
name="staticInitialize"
|
||||||
render={({ field }) => (
|
label={t("common:staticInitialize")}
|
||||||
<div className="flex items-center gap-2">
|
description={t("common:staticInitialize.description")}
|
||||||
<Switch
|
|
||||||
id="staticInitialize"
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="staticInitialize">
|
|
||||||
{t("common:staticInitialize")}
|
|
||||||
</Label>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("common:staticInitialize.description")}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isBodyMethod && needParam && (
|
{isBodyMethod && needParam && (
|
||||||
@@ -376,9 +298,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
|
|||||||
{t("common:paramName")} {t("common:optional")}
|
{t("common:paramName")} {t("common:optional")}
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>{infoIcon}</TooltipTrigger>
|
||||||
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("common:paramName.description")}</p>
|
<p>{t("common:paramName.description")}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { InfoIcon } from "lucide-react";
|
||||||
|
import { memo } from "react";
|
||||||
|
import {
|
||||||
|
type Control,
|
||||||
|
Controller,
|
||||||
|
type FieldValues,
|
||||||
|
type Path,
|
||||||
|
} from "react-hook-form";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
|
||||||
|
// Hoisted static JSX to avoid recreation on each render
|
||||||
|
const infoIcon = (
|
||||||
|
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||||
|
);
|
||||||
|
|
||||||
|
interface SwitchFieldProps<T extends FieldValues> {
|
||||||
|
readonly name: Path<T>;
|
||||||
|
readonly label: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly control: Control<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SwitchFieldInner<T extends FieldValues>({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
control,
|
||||||
|
}: SwitchFieldProps<T>) {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Switch
|
||||||
|
id={name}
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={name}>{label}</Label>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>{infoIcon}</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{description}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SwitchField = memo(SwitchFieldInner) as typeof SwitchFieldInner;
|
||||||
+65
-44
@@ -1,7 +1,7 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { HomeLayout } from "fumadocs-ui/layouts/home";
|
import { HomeLayout } from "fumadocs-ui/layouts/home";
|
||||||
import { LoaderCircle, WandSparklesIcon } from "lucide-react";
|
import { LoaderCircle, WandSparklesIcon } from "lucide-react";
|
||||||
import { useState, useTransition } from "react";
|
import { useCallback, useState, useTransition } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -28,57 +28,70 @@ import {
|
|||||||
import { transformToPostData } from "@/utils/transformer";
|
import { transformToPostData } from "@/utils/transformer";
|
||||||
import { baseOptions } from "../lib/layout.shared";
|
import { baseOptions } from "../lib/layout.shared";
|
||||||
|
|
||||||
|
const homeLayoutOptions = baseOptions();
|
||||||
|
|
||||||
|
const defaultValues: MemShellFormSchema = {
|
||||||
|
server: "Tomcat",
|
||||||
|
serverVersion: "Unknown",
|
||||||
|
targetJdkVersion: "50",
|
||||||
|
debug: false,
|
||||||
|
byPassJavaModule: false,
|
||||||
|
shellClassName: "",
|
||||||
|
shellTool: ShellToolType.Godzilla,
|
||||||
|
shellType: "Listener",
|
||||||
|
urlPattern: "/*",
|
||||||
|
godzillaPass: "",
|
||||||
|
godzillaKey: "",
|
||||||
|
commandParamName: "",
|
||||||
|
behinderPass: "",
|
||||||
|
antSwordPass: "",
|
||||||
|
headerName: "User-Agent",
|
||||||
|
headerValue: "",
|
||||||
|
injectorClassName: "",
|
||||||
|
packingMethod: "",
|
||||||
|
shrink: true,
|
||||||
|
staticInitialize: true,
|
||||||
|
shellClassBase64: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonHeaders = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const fetchJson = async <T,>(url: string): Promise<T> => {
|
||||||
|
const response = await fetch(url);
|
||||||
|
return response.json() as Promise<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchServerConfig = () =>
|
||||||
|
fetchJson<ServerConfig>(`${env.API_URL}/api/config/servers`);
|
||||||
|
|
||||||
|
const fetchMainConfig = () =>
|
||||||
|
fetchJson<MainConfig>(`${env.API_URL}/api/config`);
|
||||||
|
|
||||||
|
const fetchPackerConfig = () =>
|
||||||
|
fetchJson<PackerConfig>(`${env.API_URL}/api/config/packers`);
|
||||||
|
|
||||||
export default function MemShellPage() {
|
export default function MemShellPage() {
|
||||||
const { data: serverConfig } = useQuery<ServerConfig>({
|
const { data: serverConfig } = useQuery<ServerConfig>({
|
||||||
queryKey: ["serverConfig"],
|
queryKey: ["serverConfig"],
|
||||||
queryFn: async () => {
|
queryFn: fetchServerConfig,
|
||||||
const response = await fetch(`${env.API_URL}/api/config/servers`);
|
|
||||||
return await response.json();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: mainConfig } = useQuery<MainConfig>({
|
const { data: mainConfig } = useQuery<MainConfig>({
|
||||||
queryKey: ["mainConfig"],
|
queryKey: ["mainConfig"],
|
||||||
queryFn: async () => {
|
queryFn: fetchMainConfig,
|
||||||
const response = await fetch(`${env.API_URL}/api/config`);
|
|
||||||
return await response.json();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: packerConfig } = useQuery<PackerConfig>({
|
const { data: packerConfig } = useQuery<PackerConfig>({
|
||||||
queryKey: ["packerConfig"],
|
queryKey: ["packerConfig"],
|
||||||
queryFn: async () => {
|
queryFn: fetchPackerConfig,
|
||||||
const response = await fetch(`${env.API_URL}/api/config/packers`);
|
|
||||||
return await response.json();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation(["common", "memshell"]);
|
const { t } = useTranslation(["common", "memshell"]);
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: useYupValidationResolver(memShellFormSchema, t),
|
resolver: useYupValidationResolver(memShellFormSchema, t),
|
||||||
defaultValues: {
|
defaultValues,
|
||||||
server: "Tomcat",
|
|
||||||
serverVersion: "Unknown",
|
|
||||||
targetJdkVersion: "50",
|
|
||||||
debug: false,
|
|
||||||
byPassJavaModule: false,
|
|
||||||
shellClassName: "",
|
|
||||||
shellTool: ShellToolType.Godzilla,
|
|
||||||
shellType: "Listener",
|
|
||||||
urlPattern: "/*",
|
|
||||||
godzillaPass: "",
|
|
||||||
godzillaKey: "",
|
|
||||||
commandParamName: "",
|
|
||||||
behinderPass: "",
|
|
||||||
antSwordPass: "",
|
|
||||||
headerName: "User-Agent",
|
|
||||||
headerValue: "",
|
|
||||||
injectorClassName: "",
|
|
||||||
packingMethod: "",
|
|
||||||
shrink: true,
|
|
||||||
staticInitialize: true,
|
|
||||||
shellClassBase64: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [packResult, setPackResult] = useState<string | undefined>();
|
const [packResult, setPackResult] = useState<string | undefined>();
|
||||||
@@ -89,15 +102,13 @@ export default function MemShellPage() {
|
|||||||
const [packMethod, setPackMethod] = useState<string>("");
|
const [packMethod, setPackMethod] = useState<string>("");
|
||||||
const [isActionPending, startTransition] = useTransition();
|
const [isActionPending, startTransition] = useTransition();
|
||||||
|
|
||||||
const onSubmit = async (data: MemShellFormSchema) => {
|
const submitMemShell = useCallback(
|
||||||
startTransition(async () => {
|
async (data: MemShellFormSchema) => {
|
||||||
try {
|
try {
|
||||||
const postData = transformToPostData(data);
|
const postData = transformToPostData(data);
|
||||||
const response = await fetch(`${env.API_URL}/api/memshell/generate`, {
|
const response = await fetch(`${env.API_URL}/api/memshell/generate`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: jsonHeaders,
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(postData),
|
body: JSON.stringify(postData),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,11 +129,21 @@ export default function MemShellPage() {
|
|||||||
t("toast.generateError", { error: (error as Error).message }),
|
t("toast.generateError", { error: (error as Error).message }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
};
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(data: MemShellFormSchema) => {
|
||||||
|
startTransition(() => {
|
||||||
|
void submitMemShell(data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[submitMemShell],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HomeLayout {...baseOptions()} links={siteConfig.navLinks}>
|
<HomeLayout {...homeLayoutOptions} links={siteConfig.navLinks}>
|
||||||
<div className="container mx-auto max-w-8xl p-6">
|
<div className="container mx-auto max-w-8xl p-6">
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ export interface Suo5ShellToolConfig {
|
|||||||
headerValue?: string;
|
headerValue?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProxyShellToolConfig {
|
||||||
|
shellClassName?: string;
|
||||||
|
headerName?: string;
|
||||||
|
headerValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AntSwordShellToolConfig {
|
export interface AntSwordShellToolConfig {
|
||||||
shellClassName?: string;
|
shellClassName?: string;
|
||||||
pass?: string;
|
pass?: string;
|
||||||
@@ -137,4 +143,5 @@ export enum ShellToolType {
|
|||||||
Suo5v2 = "Suo5v2",
|
Suo5v2 = "Suo5v2",
|
||||||
NeoreGeorg = "NeoreGeorg",
|
NeoreGeorg = "NeoreGeorg",
|
||||||
Custom = "Custom",
|
Custom = "Custom",
|
||||||
|
Proxy = "Proxy",
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
@@ -1,4 +1,12 @@
|
|||||||
{
|
{
|
||||||
"title": "内存马工具",
|
"title": "内存马工具",
|
||||||
"pages": ["godzilla", "suo5", "behinder", "command", "antsword", "neoregeorg"]
|
"pages": [
|
||||||
|
"godzilla",
|
||||||
|
"suo5",
|
||||||
|
"behinder",
|
||||||
|
"command",
|
||||||
|
"antsword",
|
||||||
|
"neoregeorg",
|
||||||
|
"proxy"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
title: Proxy
|
||||||
|
---
|
||||||
|
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
||||||
|
|
||||||
|
参考地址:https://github.com/veo/wsMemShell/blob/main/static/websocketproxy.md
|
||||||
|
|
||||||
|
## WebSocket 内存马
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### 选择 Proxy
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### 生成并注入
|
||||||
|
|
||||||
|
选取合适的打包方式,并进行内存马的注入。
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### 使用 Gost 客户端尝试启动代理
|
||||||
|
|
||||||
|
https://github.com/go-gost/gost
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ ./gost -L :1080 -F "ws://127.0.0.1:8082?path=/app/proxy"
|
||||||
|
{"handler":"auto","kind":"service","level":"info","listener":"tcp","msg":"listening on [::]:1080/tcp","service":"service-0","time":"2026-01-16T23:15:55.143+08:00"}
|
||||||
|
```
|
||||||
|
|
||||||
|
尝试使用 curl 命令使用代理访问百度
|
||||||
|
|
||||||
|
```bash
|
||||||
|
> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## BypassNginxWebSocket 内存马
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step>
|
||||||
|
### 选择 Proxy 并填写参数
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step>
|
||||||
|
### 生成并注入
|
||||||
|
|
||||||
|
选取合适的打包方式,并进行内存马的注入。
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step>
|
||||||
|
### 使用 Gost 客户端尝试启动代理
|
||||||
|
|
||||||
|
https://github.com/go-gost/gost
|
||||||
|
|
||||||
|
由于 Gost 自定义请求头只能通过配置文件实现,因此创建一个 `gost.yaml`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
- name: service-0
|
||||||
|
addr: :1080
|
||||||
|
handler:
|
||||||
|
type: auto
|
||||||
|
listener:
|
||||||
|
type: tcp
|
||||||
|
chain: chain-0
|
||||||
|
|
||||||
|
chains:
|
||||||
|
- name: chain-0
|
||||||
|
hops:
|
||||||
|
- name: hop-ws
|
||||||
|
nodes:
|
||||||
|
- name: ws-tunnel
|
||||||
|
addr: 127.0.0.1:80 # 此处填写目标地址,我是用 Nginx 反代所以是 80 端口
|
||||||
|
connector:
|
||||||
|
type: http
|
||||||
|
dialer:
|
||||||
|
type: ws
|
||||||
|
metadata:
|
||||||
|
path: /app/bypass-proxy # 此处填写 WebSocket 路径
|
||||||
|
header:
|
||||||
|
User-Agent: "test" # 此处填写自定义请求头
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ ./gost -C gost.yaml
|
||||||
|
{"handler":"auto","kind":"service","level":"info","listener":"tcp","msg":"listening on [::]:1080/tcp","service":"service-0","time":"2026-01-16T23:30:04.927+08:00"}
|
||||||
|
```
|
||||||
|
|
||||||
|
尝试使用 curl 命令使用代理访问百度
|
||||||
|
|
||||||
|
```bash
|
||||||
|
> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
Reference in New Issue
Block a user