mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-23 23:41:52 +08:00
feat: support packer config
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
import { PackageIcon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PackerCombobox } from "@/components/packer/packer-combobox";
|
||||
import { PackerCustomConfigFields } from "@/components/packer/packer-custom-config-fields";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { FieldLabel, FieldSet } from "@/components/ui/field";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Field, FieldLabel, FieldSet } from "@/components/ui/field";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import {
|
||||
findPackerEntry,
|
||||
getPackerDefaultConfig,
|
||||
getPackerSchemaFields,
|
||||
normalizePackerCategories,
|
||||
} from "@/lib/packer-schema";
|
||||
import type { PackerConfig } from "@/types/memshell";
|
||||
import type { MemShellFormSchema } from "@/types/schema";
|
||||
|
||||
@@ -28,25 +35,77 @@ export default function PackageConfigCard({
|
||||
name: "server",
|
||||
});
|
||||
|
||||
const options = useMemo(() => {
|
||||
const filteredOptions = (packerConfig ?? []).filter((name) => {
|
||||
if (!shellType || shellType === " ") {
|
||||
return true;
|
||||
const packingMethod = useWatch({
|
||||
control: form.control,
|
||||
name: "packingMethod",
|
||||
});
|
||||
|
||||
const categories = useMemo(
|
||||
() => normalizePackerCategories(packerConfig),
|
||||
[packerConfig],
|
||||
);
|
||||
|
||||
const filteredCategories = useMemo(() => {
|
||||
return categories
|
||||
.map((group) => ({
|
||||
...group,
|
||||
packers: group.packers.filter((packer) => {
|
||||
if (packer.categoryAnchor) {
|
||||
return false;
|
||||
}
|
||||
const name = packer.name;
|
||||
if (!shellType || shellType === " ") {
|
||||
return true;
|
||||
}
|
||||
if (shellType.startsWith("Agent")) {
|
||||
return name.startsWith("Agent");
|
||||
}
|
||||
if ((server ?? "").startsWith("XXL")) {
|
||||
return !name.startsWith("Agent");
|
||||
}
|
||||
return (
|
||||
!name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl")
|
||||
);
|
||||
}),
|
||||
}))
|
||||
.filter((group) => group.packers.length > 0);
|
||||
}, [categories, shellType, server]);
|
||||
|
||||
const allOptionNames = useMemo(
|
||||
() =>
|
||||
filteredCategories.flatMap((group) =>
|
||||
group.packers.map((packer) => packer.name),
|
||||
),
|
||||
[filteredCategories],
|
||||
);
|
||||
|
||||
const selectedPackerEntry = useMemo(
|
||||
() =>
|
||||
findPackerEntry(filteredCategories, packingMethod) ??
|
||||
findPackerEntry(categories, packingMethod),
|
||||
[categories, filteredCategories, packingMethod],
|
||||
);
|
||||
|
||||
const selectedSchemaFields = useMemo(
|
||||
() => getPackerSchemaFields(selectedPackerEntry),
|
||||
[selectedPackerEntry],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (allOptionNames.length > 0) {
|
||||
const current = form.getValues("packingMethod");
|
||||
if (!current || !allOptionNames.includes(current)) {
|
||||
form.setValue("packingMethod", allOptionNames[0]);
|
||||
}
|
||||
if (shellType.startsWith("Agent")) {
|
||||
return name.startsWith("Agent");
|
||||
}
|
||||
if (server.startsWith("XXL")) {
|
||||
return !name.startsWith("Agent");
|
||||
}
|
||||
return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl");
|
||||
});
|
||||
form.setValue("packingMethod", filteredOptions[0]);
|
||||
return filteredOptions.map((name) => ({
|
||||
name: t(name),
|
||||
value: name,
|
||||
}));
|
||||
}, [packerConfig, shellType, server, t, form]);
|
||||
}
|
||||
}, [allOptionNames, form]);
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue(
|
||||
"packerCustomConfig",
|
||||
getPackerDefaultConfig(selectedPackerEntry) as any,
|
||||
);
|
||||
}, [form, selectedPackerEntry, packingMethod]);
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
@@ -57,32 +116,30 @@ export default function PackageConfigCard({
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{options.length > 0 ? (
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="packingMethod"
|
||||
render={({ field }) => (
|
||||
<FieldSet>
|
||||
<FieldLabel>{t("packerMethod")}</FieldLabel>
|
||||
<RadioGroup
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
defaultValue={options[0].value}
|
||||
onValueChange={field.onChange}
|
||||
className="grid grid-cols-2 md:grid-cols-3"
|
||||
>
|
||||
{options.map(({ name, value }) => (
|
||||
<div key={value} className="flex items-center space-x-3">
|
||||
<RadioGroupItem value={value} id={value} />
|
||||
<FieldLabel className="text-xs" htmlFor={value}>
|
||||
{name}
|
||||
</FieldLabel>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FieldSet>
|
||||
)}
|
||||
/>
|
||||
{allOptionNames.length > 0 ? (
|
||||
<>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="packingMethod"
|
||||
render={({ field }) => (
|
||||
<Field className="gap-1">
|
||||
<FieldLabel>{t("packerMethod")}</FieldLabel>
|
||||
<PackerCombobox
|
||||
categories={filteredCategories}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
placeholder={t("selectPacker", {
|
||||
defaultValue: "Select packer",
|
||||
})}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
<PackerCustomConfigFields
|
||||
form={form}
|
||||
fields={selectedSchemaFields}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex items-center justify-center p-4 gap-4 h-50">
|
||||
<Spinner />
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
import { DownloadIcon } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CodeViewer from "@/components/code-viewer";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { base64ToBytes, downloadBytes, downloadContent } from "@/lib/utils";
|
||||
|
||||
export function MultiPackResult({
|
||||
allPackResults,
|
||||
packMethod,
|
||||
shellClassName,
|
||||
height = 350,
|
||||
}: Readonly<{
|
||||
allPackResults: object | undefined;
|
||||
packMethod: string;
|
||||
shellClassName?: string;
|
||||
height?: number;
|
||||
}>) {
|
||||
const showCode = packMethod === "JSP";
|
||||
const { t } = useTranslation();
|
||||
const packResults = allPackResults as Record<string, string> | undefined;
|
||||
const packMethods = useMemo(
|
||||
() => Object.keys(packResults ?? {}),
|
||||
[packResults],
|
||||
);
|
||||
|
||||
const [selectedMethod, setSelectedMethod] = useState(
|
||||
() => packMethods[0] ?? "",
|
||||
);
|
||||
|
||||
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 =
|
||||
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
|
||||
if (packMethod === "JSP") {
|
||||
const fileExtension = selectedMethod.includes("JSPX") ? ".jspx" : ".jsp";
|
||||
const content = new Blob([packResult], { type: "text/plain" });
|
||||
return downloadContent(content, fileName, fileExtension);
|
||||
} else if (
|
||||
packMethod === "JavaDeserialize" ||
|
||||
packMethod.includes("Hessian")
|
||||
) {
|
||||
const content = new Blob([base64ToBytes(packResult)], {
|
||||
type: "application/octet-stream",
|
||||
});
|
||||
return downloadContent(content, fileName, ".data");
|
||||
} else if (packMethod === "Base64") {
|
||||
const base64Content = packResults?.[packMethods[0]] ?? "";
|
||||
return downloadBytes(base64Content, shellClassName);
|
||||
}
|
||||
}, [
|
||||
packMethod,
|
||||
packMethods,
|
||||
packResult,
|
||||
packResults,
|
||||
selectedMethod,
|
||||
shellClassName,
|
||||
]);
|
||||
|
||||
return (
|
||||
<CodeViewer
|
||||
code={packResult ?? ""}
|
||||
header={
|
||||
<div className="flex items-center justify-between text-xs gap-2">
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
setSelectedMethod(value as string);
|
||||
}}
|
||||
value={selectedMethod}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs [&_svg]:h-4 [&_svg]:w-4">
|
||||
<span className="text-muted-foreground">
|
||||
{t("common:packerMethod")}:
|
||||
</span>
|
||||
<SelectValue data-placeholder={t("common:placeholders.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{packMethods.map((method) => (
|
||||
<SelectItem key={method} value={method} className="text-xs">
|
||||
{method}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-muted-foreground">({packResult?.length})</span>
|
||||
</div>
|
||||
}
|
||||
button={
|
||||
packMethod === "JSP" ||
|
||||
packMethod === "Base64" ||
|
||||
packMethod === "JavaDeserialize" ||
|
||||
packMethod.includes("Hessian") ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
type="button"
|
||||
className="h-7 w-7 [&_svg]:h-4 [&_svg]:w-4"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
wrapLongLines={!showCode}
|
||||
showLineNumbers={showCode}
|
||||
language={showCode ? "java" : "text"}
|
||||
height={height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -3,32 +3,19 @@ import CodeViewer from "@/components/code-viewer";
|
||||
import type { MemShellResult } from "@/types/memshell";
|
||||
import { AgentResult } from "./agent";
|
||||
import { JarResult } from "./jar-result";
|
||||
import { MultiPackResult } from "./multi-packer";
|
||||
|
||||
export function ResultComponent({
|
||||
packResult,
|
||||
allPackResults,
|
||||
packMethod,
|
||||
generateResult,
|
||||
}: Readonly<{
|
||||
packResult: string | undefined;
|
||||
allPackResults: Map<string, string> | undefined;
|
||||
packMethod: string;
|
||||
generateResult?: MemShellResult;
|
||||
}>) {
|
||||
const showCode = packMethod === "JSP";
|
||||
const isAgent = packMethod.startsWith("Agent");
|
||||
const isJar = packMethod.endsWith("Jar");
|
||||
const { t } = useTranslation();
|
||||
if (allPackResults) {
|
||||
return (
|
||||
<MultiPackResult
|
||||
allPackResults={allPackResults}
|
||||
packMethod={packMethod}
|
||||
shellClassName={generateResult?.injectorClassName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isAgent) {
|
||||
return (
|
||||
<AgentResult
|
||||
@@ -52,16 +39,16 @@ export function ResultComponent({
|
||||
<CodeViewer
|
||||
code={packResult ?? ""}
|
||||
header={
|
||||
<div className="flex items-center justify-between text-xs gap-2">
|
||||
<div className="flex items-center justify-between text-sm gap-2">
|
||||
<span>
|
||||
{t("common:packerMethod")}:{packMethod}
|
||||
</span>
|
||||
<span className="text-muted-foreground">({packResult?.length})</span>
|
||||
</div>
|
||||
}
|
||||
wrapLongLines={!showCode}
|
||||
showLineNumbers={showCode}
|
||||
language={showCode ? "java" : "text"}
|
||||
wrapLongLines={true}
|
||||
showLineNumbers={false}
|
||||
language={"text"}
|
||||
height={350}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -13,12 +13,10 @@ import { ResultComponent } from "./results/result-component";
|
||||
|
||||
export default function ShellResult({
|
||||
packResult,
|
||||
allPackResults,
|
||||
packMethod,
|
||||
generateResult,
|
||||
}: Readonly<{
|
||||
packResult: string | undefined;
|
||||
allPackResults: Map<string, string> | undefined;
|
||||
packMethod: string;
|
||||
generateResult?: MemShellResult;
|
||||
}>) {
|
||||
@@ -26,7 +24,7 @@ export default function ShellResult({
|
||||
if (!generateResult) {
|
||||
return <QuickUsage />;
|
||||
}
|
||||
const height = 800;
|
||||
const height = 600;
|
||||
return (
|
||||
<Tabs defaultValue="packResult">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
@@ -42,7 +40,6 @@ export default function ShellResult({
|
||||
<BasicInfo generateResult={generateResult} />
|
||||
<ResultComponent
|
||||
packResult={packResult}
|
||||
allPackResults={allPackResults}
|
||||
packMethod={packMethod}
|
||||
generateResult={generateResult}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user