From 6e863d9dc9e44272b53412774e93acbcb8a2d1ff Mon Sep 17 00:00:00 2001 From: ReaJason Date: Tue, 27 May 2025 03:42:41 +0800 Subject: [PATCH] refactor: use yup custom validator --- web/bun.lockb | Bin 208164 -> 208748 bytes web/package.json | 14 ++-- web/src/config.ts | 59 +++++++++++++--- web/src/i18n/en.json | 4 +- web/src/i18n/zh-CN.json | 6 +- web/src/pages/index.tsx | 10 ++- web/src/types/schema.ts | 131 ++++++++++++++++++++++++++++------- web/src/utils/transformer.ts | 24 +------ 8 files changed, 176 insertions(+), 72 deletions(-) diff --git a/web/bun.lockb b/web/bun.lockb index 23b19a21072afb5e873229aab57bdd036dd62ca2..9b9571c9e87b3063dba8c44b99b999d3e9d1dad3 100755 GIT binary patch delta 40508 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 aj9rBbeirC_yt?dXq5SZ*~+$y8Y5y% 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 diff --git a/web/package.json b/web/package.json index 2aaf5b0c..8f2ab5d1 100644 --- a/web/package.json +++ b/web/package.json @@ -20,7 +20,7 @@ "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^19.1.5", "@types/react-syntax-highlighter": "^15.5.13", - "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-react": "^4.5.0", "rimraf": "^6.0.1", "tailwindcss": "^4.1.7", "typescript": "^5.8.3", @@ -56,13 +56,13 @@ "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.7", - "@tanstack/react-query": "^5.76.1", + "@tanstack/react-query": "^5.77.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", - "i18next": "^25.2.0", + "i18next": "^25.2.1", "input-otp": "^1.4.2", "lucide-react": "^0.511.0", "react": "^19.1.0", @@ -70,10 +70,10 @@ "react-day-picker": "9.7.0", "react-dom": "^19.1.0", "react-hook-form": "^7.56.4", - "react-i18next": "^15.5.1", + "react-i18next": "^15.5.2", "react-resizable-panels": "^3.0.2", - "react-router": "^7.6.0", - "react-router-dom": "^7.6.0", + "react-router": "^7.6.1", + "react-router-dom": "^7.6.1", "react-syntax-highlighter": "^15.6.1", "recharts": "^2.15.3", "sonner": "^2.0.3", @@ -81,7 +81,7 @@ "tailwind-scrollbar": "^4.0.2", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.2", - "zod": "^3.25.20" + "yup": "^1.6.1" }, "trustedDependencies": ["@biomejs/biome"] } diff --git a/web/src/config.ts b/web/src/config.ts index 88ac4e44..ef7fc1b1 100644 --- a/web/src/config.ts +++ b/web/src/config.ts @@ -1,11 +1,51 @@ -import * as z from "zod"; +import * as yup from "yup"; -const EnvSchema = z.object({ - API_URL: z.optional(z.string()), - BASE_PATH: z.optional(z.string()), - MODE: z.string(), +const EnvSchema = yup.object({ + API_URL: yup.string().optional(), + BASE_PATH: yup.string().optional(), + MODE: yup.string().required(), }); +type EnvSchema = yup.InferType; +function safeParseYup(schema: yup.ObjectSchema, data: unknown) { + try { + const validatedData = schema.validateSync(data, { + abortEarly: false, + stripUnknown: true + }); + + return { + success: true as const, + data: validatedData as T, + error: undefined, + }; + } catch (error) { + if (error instanceof yup.ValidationError) { + return { + success: false as const, + data: undefined, + error: { + issues: error.inner.map(err => ({ + path: err.path?.split('.') || [], + message: err.message, + code: err.type ?? 'validation_error', + })), + message: error.message, + }, + }; + } + + return { + success: false as const, + data: undefined, + error: { + issues: [], + message: 'Unknown validation error', + }, + }; + } +} + const createEnv = () => { // @ts-ignore const envVars = Object.entries(import.meta.env).reduce>((acc, curr) => { @@ -20,14 +60,15 @@ const createEnv = () => { } return acc; }, {}); - const parsedEnv = EnvSchema.safeParse(envVars); + console.log(envVars) + const parsedEnv = safeParseYup(EnvSchema, envVars); if (!parsedEnv.success) { throw new Error( `Invalid env provided. The following variables are missing or invalid: -${Object.entries(parsedEnv.error.flatten().fieldErrors) - .map(([k, v]) => `- ${k}: ${v}`) - .join("\n")} +${parsedEnv.error.issues + .map(({ path, message }) => `- ${path}: ${message}`) + .join("\n")} `, ); } diff --git a/web/src/i18n/en.json b/web/src/i18n/en.json index 95a86d43..790aa8a0 100644 --- a/web/src/i18n/en.json +++ b/web/src/i18n/en.json @@ -130,6 +130,7 @@ "agent-move-to-target": "Move MemShellAgent.jar and jattach to target host", "agent-move-to-target1": "Move MemShellAgent.jar to target host", "servletUrlPattern": "Servlet type requires a specific URL Pattern, e.g., /hello_servlet", + "specificUrlPattern": "URL Pattern must be specified, e.g., /hello", "shellBytesEmpty": "Shell bytes is empty, please generate shell first", "shellToolNotSelected": "Please select a shell tool type first", "targetServerNotFound": "Target server not found?", @@ -143,5 +144,6 @@ "updateAvailableTooltip": "Click to Open Github Release Page ( v{{currentVersion}} -> v{{latestVersion}})" }, "generator": "Generator", - "about": "About" + "about": "About", + "classNameOptions": "classNameOptions" } diff --git a/web/src/i18n/zh-CN.json b/web/src/i18n/zh-CN.json index a147c849..dd3af3a5 100644 --- a/web/src/i18n/zh-CN.json +++ b/web/src/i18n/zh-CN.json @@ -136,12 +136,14 @@ "targetServerRequest": "请求适配", "try-to-use-shell": "尝试利用内存马", "waitingForGeneration": "// 等待填写参数生成中...", - "customShellClass": "请输入自定义内存马类,base64 或类文件" + "customShellClass": "请输入自定义内存马类,base64 或类文件", + "specificUrlPattern": "必须指定 URL Pattern,例如 /hello" }, "version": { "updateAvailable": "有可用升级", "updateAvailableTooltip": "点击前往 GitHub Release ( v{{currentVersion}} -> v{{latestVersion}})" }, "about": "关于", - "generator": "生成器" + "generator": "生成器", + "classNameOptions": "类名配置项" } diff --git a/web/src/pages/index.tsx b/web/src/pages/index.tsx index f1770e9b..dc66e56f 100644 --- a/web/src/pages/index.tsx +++ b/web/src/pages/index.tsx @@ -4,7 +4,7 @@ import { ShellResult } from "@/components/shell-result.tsx"; import { Button } from "@/components/ui/button"; import { Form } from "@/components/ui/form.tsx"; import { env } from "@/config.ts"; -import { FormSchema, formSchema } from "@/types/schema.ts"; +import { FormSchema, formSchema, useYupValidationResolver } from "@/types/schema.ts"; import { APIErrorResponse, GenerateResponse, @@ -14,8 +14,7 @@ import { ServerConfig, ShellToolType, } from "@/types/shell.ts"; -import { customValidation, transformToPostData } from "@/utils/transformer.ts"; -import { zodResolver } from "@hookform/resolvers/zod"; +import { transformToPostData } from "@/utils/transformer.ts"; import { useQuery } from "@tanstack/react-query"; import { LoaderCircle, WandSparklesIcon } from "lucide-react"; import { useState, useTransition } from "react"; @@ -52,8 +51,8 @@ export default function IndexPage() { }); const { t } = useTranslation(); - const form = useForm({ - resolver: zodResolver(formSchema), + const form = useForm({ + resolver: useYupValidationResolver(formSchema, t), defaultValues: { server: urlParams.server ?? "Tomcat", targetJdkVersion: urlParams.targetJdkVersion ?? "50", @@ -86,7 +85,6 @@ export default function IndexPage() { const onSubmit = async (data: FormSchema) => { startTransition(async () => { try { - customValidation(t, data); const postData = transformToPostData(data); const response = await fetch(`${env.API_URL}/generate`, { method: "POST", diff --git a/web/src/types/schema.ts b/web/src/types/schema.ts index 3d31145b..cc80030e 100644 --- a/web/src/types/schema.ts +++ b/web/src/types/schema.ts @@ -1,27 +1,110 @@ -import * as z from "zod"; +import { TFunction } from "i18next"; +import { useCallback } from "react"; +import { FieldErrors } from "react-hook-form"; +import * as yup from "yup"; +import { ShellToolType } from "./shell"; -export const formSchema = z.object({ - server: z.string().min(1), - targetJdkVersion: z.optional(z.string()), - debug: z.optional(z.boolean()), - bypassJavaModule: z.optional(z.boolean()), - shellClassName: z.string().optional(), - shellTool: z.string().min(1), - shellType: z.string().min(1), - urlPattern: z.optional(z.string()), - godzillaPass: z.optional(z.string()), - godzillaKey: z.optional(z.string()), - behinderPass: z.optional(z.string()), - antSwordPass: z.optional(z.string()), - commandParamName: z.optional(z.string()), - implementationClass: z.optional(z.string()), - headerName: z.optional(z.string()), - headerValue: z.optional(z.string()), - injectorClassName: z.optional(z.string()), - packingMethod: z.string().min(1), - shrink: z.optional(z.boolean()), - shellClassBase64: z.optional(z.string()), - encryptor: z.optional(z.string()), +export const formSchema = yup.object({ + server: yup.string().required().min(1), + targetJdkVersion: yup.string().optional(), + debug: yup.boolean().optional(), + bypassJavaModule: yup.boolean().optional(), + shellClassName: yup.string().optional(), + shellTool: yup.string().required().min(1), + shellType: yup.string().required().min(1), + urlPattern: yup.string().optional(), + godzillaPass: yup.string().optional(), + godzillaKey: yup.string().optional(), + behinderPass: yup.string().optional(), + antSwordPass: yup.string().optional(), + commandParamName: yup.string().optional(), + implementationClass: yup.string().optional(), + headerName: yup.string().optional(), + headerValue: yup.string().optional(), + injectorClassName: yup.string().optional(), + packingMethod: yup.string().required().min(1), + shrink: yup.boolean().optional(), + shellClassBase64: yup.string().optional(), + encryptor: yup.string().optional(), }); -export type FormSchema = z.infer; +interface ValidationResult { + values: FormSchema; + errors: FieldErrors; +} + +const urlPatternIsNeeded = (shellType: string) => { + return ( + shellType.endsWith("Servlet") || + shellType.endsWith("ControllerHandler") || + shellType === "HandlerMethod" || + shellType === "HandlerFunction" || + shellType.endsWith("WebSocket") + ); +}; + +const isInvalidUrl = (urlPattern: string | undefined) => + urlPattern === "/" || urlPattern === "/*" || !urlPattern?.startsWith("/") || !urlPattern; + +export const useYupValidationResolver = (validationSchema: yup.ObjectSchema, t: TFunction) => + useCallback( + async (data: FormSchema): Promise => { + try { + const values = (await validationSchema.validate(data, { + abortEarly: false, + })) as FormSchema; + + const urlPattern: keyof FormSchema = "urlPattern"; + const shellClassBase64: keyof FormSchema = "shellClassBase64"; + const errors = {} as any; + + if (urlPatternIsNeeded(values?.shellType) && isInvalidUrl(values?.urlPattern)) { + errors[urlPattern] = { + type: "custom", + message: t("tips.specificUrlPattern"), + }; + } + if (values.shellTool === ShellToolType.Custom && !values.shellClassBase64) { + errors[shellClassBase64] = { + type: "custom", + message: t("tips.customShellClass"), + }; + } + + return { + values, + errors, + }; + } catch (errors) { + if (errors instanceof yup.ValidationError) { + return { + values: {} as FormSchema, + errors: errors.inner.reduce( + (allErrors, currentError) => ({ + // biome-ignore lint/performance/noAccumulatingSpread: + ...allErrors, + [currentError.path as keyof FormSchema]: { + type: currentError.type ?? "validation", + message: currentError.message, + }, + }), + {}, + ), + }; + } + + return { + values: {} as FormSchema, + errors: { + server: { + type: "unknown", + message: "An unexpected validation error occurred", + }, + }, + }; + } + }, + [validationSchema, t], + ); + +export type FormSchema = yup.InferType; diff --git a/web/src/utils/transformer.ts b/web/src/utils/transformer.ts index fa595857..e073c960 100644 --- a/web/src/utils/transformer.ts +++ b/web/src/utils/transformer.ts @@ -1,27 +1,5 @@ import { FormSchema } from "@/types/schema.ts"; -import { InjectorConfig, ShellConfig, ShellToolConfig, ShellToolType } from "@/types/shell.ts"; -import { TFunction } from "i18next"; - -export function customValidation(t: TFunction<"translation", undefined>, values: FormSchema) { - if (values.shellType.endsWith("Servlet") && (values.urlPattern === "/*" || !values.urlPattern)) { - throw new Error(t("tips.servletUrlPattern")); - } - - if (values.shellType.endsWith("ControllerHandler") && (values.urlPattern === "/*" || !values.urlPattern)) { - throw new Error(t("tips.controllerUrlPattern")); - } - - if ( - (values.shellType === "HandlerMethod" || values.shellType === "HandlerFunction") && - (values.urlPattern === "/*" || !values.urlPattern) - ) { - throw new Error(t("tips.handlerUrlPattern")); - } - - if (values.shellTool === ShellToolType.Custom && !values.shellClassBase64) { - throw new Error(t("tips.customShellClass")); - } -} +import { InjectorConfig, ShellConfig, ShellToolConfig } from "@/types/shell.ts"; export function transformToPostData(formValue: FormSchema) { const shellConfig: ShellConfig = {