mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-21 22:50:42 +08:00
feat(web): support download button for some packers
This commit is contained in:
Binary file not shown.
+22
-22
@@ -15,42 +15,42 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.1.4",
|
"@biomejs/biome": "2.1.4",
|
||||||
"@react-router/dev": "^7.9.1",
|
"@react-router/dev": "^7.9.5",
|
||||||
"@types/node": "^24.5.2",
|
"@types/node": "^24.10.0",
|
||||||
"@types/react": "^19.1.13",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.7",
|
"@types/react-copy-to-clipboard": "^5.0.7",
|
||||||
"@types/react-dom": "^19.1.9",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@vitejs/plugin-react": "^5.0.3",
|
"@vitejs/plugin-react": "^5.1.0",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.1.0",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.16",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.1.6",
|
"vite": "^7.1.12",
|
||||||
"vite-bundle-visualizer": "^1.2.1"
|
"vite-bundle-visualizer": "^1.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
"@tanstack/react-query": "^5.89.0",
|
"@tanstack/react-query": "^5.90.6",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^12.23.16",
|
"framer-motion": "^12.23.24",
|
||||||
"i18next": "^25.5.2",
|
"i18next": "^25.6.0",
|
||||||
"lucide-react": "^0.539.0",
|
"lucide-react": "^0.539.0",
|
||||||
"motion": "^12.23.16",
|
"motion": "^12.23.24",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "^19.1.1",
|
"react": "^19.2.0",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.2.0",
|
||||||
"react-hook-form": "^7.62.0",
|
"react-hook-form": "^7.66.0",
|
||||||
"react-i18next": "^15.7.3",
|
"react-i18next": "^15.7.4",
|
||||||
"react-router": "^7.9.1",
|
"react-router": "^7.9.5",
|
||||||
"react-router-dom": "^7.9.1",
|
"react-router-dom": "^7.9.5",
|
||||||
"react-syntax-highlighter": "^15.6.6",
|
"react-syntax-highlighter": "^15.6.6",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tw-animate-css": "^1.3.8",
|
"tw-animate-css": "^1.4.0",
|
||||||
"yup": "^1.7.0"
|
"yup": "^1.7.1"
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
"@biomejs/biome"
|
"@biomejs/biome"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { DownloadIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, 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 {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -8,32 +10,67 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { base64ToBytes, downloadBytes, downloadContent } from "@/lib/utils";
|
||||||
|
|
||||||
export function MultiPackResult({
|
export function MultiPackResult({
|
||||||
allPackResults,
|
allPackResults,
|
||||||
packMethod,
|
packMethod,
|
||||||
|
shellClassName,
|
||||||
height = 350,
|
height = 350,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
allPackResults: object | undefined;
|
allPackResults: object | undefined;
|
||||||
packMethod: string;
|
packMethod: string;
|
||||||
|
shellClassName?: string;
|
||||||
height?: number;
|
height?: number;
|
||||||
}>) {
|
}>) {
|
||||||
const showCode = packMethod === "JSP";
|
const showCode = packMethod === "JSP";
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const packMethods = Object.keys(allPackResults ?? {});
|
const packMethods = Object.keys(allPackResults ?? {});
|
||||||
|
|
||||||
const [selectedMethod, setSelectedMethod] = useState(packMethods[0]);
|
const [selectedMethod, setSelectedMethod] = useState(packMethods[0]);
|
||||||
const [packResult, setPackResult] = useState(
|
const [packResult, setPackResult] = useState(
|
||||||
allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "",
|
allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "",
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const methods = Object.keys(allPackResults ?? {});
|
const newPackMethods = Object.keys(allPackResults ?? {});
|
||||||
const firstMethod = methods[0];
|
if (!newPackMethods.includes(selectedMethod)) {
|
||||||
setSelectedMethod(firstMethod);
|
const newSelectedMethod = newPackMethods[0];
|
||||||
setPackResult(
|
setSelectedMethod(newSelectedMethod);
|
||||||
allPackResults?.[firstMethod as keyof typeof allPackResults] ?? "",
|
setPackResult(
|
||||||
);
|
allPackResults?.[newSelectedMethod as keyof typeof allPackResults] ??
|
||||||
}, [allPackResults]);
|
"",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setPackResult(
|
||||||
|
allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [allPackResults, selectedMethod]);
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
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 =
|
||||||
|
allPackResults?.[
|
||||||
|
Object.keys(allPackResults)[0] as keyof typeof allPackResults
|
||||||
|
] ?? "";
|
||||||
|
return downloadBytes(base64Content, shellClassName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeViewer
|
<CodeViewer
|
||||||
@@ -66,6 +103,22 @@ export function MultiPackResult({
|
|||||||
<span className="text-muted-foreground">({packResult?.length})</span>
|
<span className="text-muted-foreground">({packResult?.length})</span>
|
||||||
</div>
|
</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}
|
wrapLongLines={!showCode}
|
||||||
showLineNumbers={showCode}
|
showLineNumbers={showCode}
|
||||||
language={showCode ? "java" : "text"}
|
language={showCode ? "java" : "text"}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export function ResultComponent({
|
|||||||
<MultiPackResult
|
<MultiPackResult
|
||||||
allPackResults={allPackResults}
|
allPackResults={allPackResults}
|
||||||
packMethod={packMethod}
|
packMethod={packMethod}
|
||||||
|
shellClassName={generateResult?.injectorClassName}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export default function ShellResult({
|
|||||||
{allPackResults && (
|
{allPackResults && (
|
||||||
<MultiPackResult
|
<MultiPackResult
|
||||||
allPackResults={allPackResults}
|
allPackResults={allPackResults}
|
||||||
|
shellClassName={generateResult?.shellClassName}
|
||||||
packMethod={packMethod}
|
packMethod={packMethod}
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+23
-10
@@ -5,24 +5,38 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadBytes(
|
export function downloadContent(
|
||||||
base64String: string,
|
content: Blob,
|
||||||
className?: string,
|
fileName: string,
|
||||||
jarName?: string,
|
fileExtension: string,
|
||||||
) {
|
) {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = URL.createObjectURL(content);
|
||||||
|
link.download = `${fileName}${fileExtension}`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function base64ToBytes(base64String: string) {
|
||||||
const byteCharacters = atob(base64String);
|
const byteCharacters = atob(base64String);
|
||||||
const byteNumbers = new Array(byteCharacters.length);
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
for (let i = 0; i < byteCharacters.length; i++) {
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||||
}
|
}
|
||||||
const byteArray = new Uint8Array(byteNumbers);
|
return new Uint8Array(byteNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
// Create a Blob from the byte array
|
export function downloadBytes(
|
||||||
|
base64String: string,
|
||||||
|
className?: string,
|
||||||
|
jarName?: string,
|
||||||
|
) {
|
||||||
|
const byteArray = base64ToBytes(base64String);
|
||||||
const blob = new Blob([byteArray], {
|
const blob = new Blob([byteArray], {
|
||||||
type: className ? "application/java-vm" : "application/java-archive",
|
type: className ? "application/java-vm" : "application/java-archive",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a download link
|
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = window.URL.createObjectURL(blob);
|
link.href = window.URL.createObjectURL(blob);
|
||||||
link.download = className
|
link.download = className
|
||||||
@@ -39,15 +53,14 @@ export function formatBytes(bytes: number) {
|
|||||||
if (Number.isNaN(bytes) || !Number.isFinite(bytes)) return "N/A";
|
if (Number.isNaN(bytes) || !Number.isFinite(bytes)) return "N/A";
|
||||||
|
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
const sizes = ["Bytes", "KB", "MB"]; // Add more units like GB, TB if needed
|
const sizes = ["Bytes", "KB", "MB"];
|
||||||
|
|
||||||
if (bytes < k) {
|
if (bytes < k) {
|
||||||
return `${bytes.toFixed(0)} ${sizes[0]}`; // Bytes, no decimal
|
return `${bytes.toFixed(0)} ${sizes[0]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
// Prefer MB if KB value is 1000 or more, or if it's already in MB (i >= 2)
|
|
||||||
if (i >= 2 || bytes / k ** 1 >= 1000) {
|
if (i >= 2 || bytes / k ** 1 >= 1000) {
|
||||||
const mbValue = Number.parseFloat((bytes / k ** 2).toFixed(2));
|
const mbValue = Number.parseFloat((bytes / k ** 2).toFixed(2));
|
||||||
return `${mbValue} ${sizes[2]}`;
|
return `${mbValue} ${sizes[2]}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user