diff --git a/app/protos/grpc.proto b/app/protos/grpc.proto index 8ceaecd5cd..8292b2a2db 100644 --- a/app/protos/grpc.proto +++ b/app/protos/grpc.proto @@ -4098,6 +4098,7 @@ message FuzzerRequest { bool IsPause = 53; repeated MutateMethod MutateMethods = 54; + bool SetPauseStatus = 55; } message MutateMethod { @@ -4210,6 +4211,8 @@ message FuzzerResponse { string TooLargeResponseHeaderFile = 50; string TooLargeResponseBodyFile = 51; bool DisableRenderStyles = 52; + + string RuntimeID = 53; } message RedirectHTTPFlow { @@ -4288,6 +4291,9 @@ message QueryHTTPFlowRequest { repeated string IncludeSuffix = 36; repeated string ExcludeSuffix = 37; repeated string ExcludeContentType = 38; + + bool WithPayload = 39; + repeated string RuntimeIDs = 40; } message ExportHTTPFlowsRequest { @@ -4388,6 +4394,9 @@ message HTTPFlow { string TooLargeResponseHeaderFile = 44; string TooLargeResponseBodyFile = 45; bool DisableRenderStyles = 46; + + // http_flows 与 web_fuzzer_responses 表关联下取到Payload + repeated string Payloads = 47; } message FuzzableParam { diff --git a/app/renderer/src/main/src/components/DataExport/DataExport.tsx b/app/renderer/src/main/src/components/DataExport/DataExport.tsx index 76fa43720c..fa519b65e7 100644 --- a/app/renderer/src/main/src/components/DataExport/DataExport.tsx +++ b/app/renderer/src/main/src/components/DataExport/DataExport.tsx @@ -310,17 +310,18 @@ interface ExportSelectProps { fileName?: string /* limit */ pageSize?: number + initCheckValue?: string[] } // 导出字段选择 export const ExportSelect: React.FC = (props) => { - const {exportValue, fileName, setExportTitle, exportKey, getData, pageSize} = props + const {exportValue, fileName, setExportTitle, exportKey, getData, pageSize, initCheckValue} = props const [checkValue, setCheckValue] = useState([]) useEffect(() => { getRemoteValue(exportKey).then((setting) => { if (!setting) { // 第一次进入 默认勾选所有导出字段 setExportTitle(exportValue as string[]) - setCheckValue(exportValue) + setCheckValue(initCheckValue || exportValue) } else { const values = JSON.parse(setting) setCheckValue(values?.checkedValues) diff --git a/app/renderer/src/main/src/components/HTTPFlowTable/HTTPFlowTable.tsx b/app/renderer/src/main/src/components/HTTPFlowTable/HTTPFlowTable.tsx index 109eb9deee..83f76210d2 100644 --- a/app/renderer/src/main/src/components/HTTPFlowTable/HTTPFlowTable.tsx +++ b/app/renderer/src/main/src/components/HTTPFlowTable/HTTPFlowTable.tsx @@ -76,7 +76,7 @@ import {newWebsocketFuzzerTab} from "@/pages/websocket/WebsocketFuzzer" import {YakitEditorKeyCode} from "../yakitUI/YakitEditor/YakitEditorType" import {YakitSystem} from "@/yakitGVDefine" import {convertKeyboard} from "../yakitUI/YakitEditor/editorUtils" -import { serverPushStatus } from "@/utils/duplex/duplex" +import {serverPushStatus} from "@/utils/duplex/duplex" const {ipcRenderer} = window.require("electron") @@ -312,7 +312,7 @@ export interface HTTPFlowTableProp { historyId?: string // 筛选控件隐藏 onlyShowSearch?: boolean - // 此控件显示的页面 + // 表格主要应用类型分为History和mitm两种,若其他页面需要使用此表格,pageType需要默认为History,需要额外再新加参数区分进行使用 pageType?: HTTPHistorySourcePageType searchURL?: string includeInUrl?: string | string[] @@ -324,6 +324,10 @@ export interface HTTPFlowTableProp { toPlugin?: boolean /** RuntimeId 流量过滤条件(RuntimeId) */ runTimeId?: string + /** 是否为webFuzzer使用 */ + toWebFuzzer?: boolean + /** 是否显示批量操作 */ + showBatchActions?: boolean } export const StatusCodeToColor = (code: number) => { @@ -622,7 +626,9 @@ export const HTTPFlowTable = React.memo((props) => { titleHeight = 38, containerClassName = "", toPlugin = false, - runTimeId + runTimeId, + toWebFuzzer = false, + showBatchActions = true } = props const [data, setData, getData] = useGetState([]) const [color, setColor] = useState([]) @@ -632,7 +638,10 @@ export const HTTPFlowTable = React.memo((props) => { SourceType: "mitm", Tags: [] }), - SourceType: props.params?.SourceType || "mitm" + SourceType: props.params?.SourceType || "mitm", + WithPayload: toWebFuzzer, + RuntimeIDs: toWebFuzzer && runTimeId ? runTimeId.split(",") : undefined, + RuntimeId: !toWebFuzzer ? runTimeId || undefined : undefined }) const [tagsFilter, setTagsFilter] = useState([]) const [pagination, setPagination] = useState({ @@ -744,8 +753,8 @@ export const HTTPFlowTable = React.memo((props) => { return item.Id + "" === obj.id }) if (scrollToIndex !== undefined) { - // 加随机值触发更新渲染执行表格跳转方法 - setScrollToIndex(scrollToIndex + '_' + Math.random()) + // 加随机值触发更新渲染执行表格跳转方法 + setScrollToIndex(scrollToIndex + "_" + Math.random()) } } } catch (error) {} @@ -911,9 +920,7 @@ export const HTTPFlowTable = React.memo((props) => { ...params, ...filter, Tags: [...tagsFilter], - bodyLength: !!(afterBodyLength || beforeBodyLength), // 用来判断响应长度的icon颜色是否显示蓝色 - AfterBodyLength: afterBodyLength, - BeforeBodyLength: beforeBodyLength + bodyLength: !!(afterBodyLength || beforeBodyLength) // 用来判断响应长度的icon颜色是否显示蓝色 }) sortRef.current = sort setTimeout(() => { @@ -971,6 +978,9 @@ export const HTTPFlowTable = React.memo((props) => { delete copyQuery.Pagination delete copyQuery.AfterId delete copyQuery.BeforeId + delete copyQuery.WithPayload + delete copyQuery.RuntimeIDs + delete copyQuery.AfterUpdatedAt copyQuery.Color = copyQuery.Color ? copyQuery.Color : [] copyQuery.StatusCode = copyQuery.StatusCode ? copyQuery.StatusCode.join(",") : "" setQueryParams(JSON.stringify(copyQuery)) @@ -982,25 +992,23 @@ export const HTTPFlowTable = React.memo((props) => { clearInterval(extraTimerRef.current) } }, []) + // 方法请求 const getDataByGrpc = useMemoizedFn((query, type: "top" | "bottom" | "update" | "offset") => { // 插件执行中流量数据必有runTimeId - if (toPlugin && !runTimeId) { + if ((toPlugin || toWebFuzzer) && !runTimeId) { setTimeout(() => { setLoading(false) isGrpcRef.current = false }, 100) return } - updateQueryParams(query) - query = query.StatusCode ? {...query, StatusCode: query.StatusCode.join(",")} : query if (isGrpcRef.current) return isGrpcRef.current = true - if (runTimeId) query.RuntimeId = runTimeId - // 查询数据 + updateQueryParams(query) ipcRenderer .invoke("QueryHTTPFlows", query) .then((rsp: YakQueryHTTPFlowResponse) => { @@ -1009,7 +1017,7 @@ export const HTTPFlowTable = React.memo((props) => { if (type === "top") { if (newData.length <= 0) { // 没有数据 - serverPushStatus&&setIsLoop(false) + serverPushStatus && setIsLoop(false) return } @@ -1026,7 +1034,7 @@ export const HTTPFlowTable = React.memo((props) => { } else if (type === "bottom") { if (newData.length <= 0) { // 没有数据 - serverPushStatus&&setIsLoop(false) + serverPushStatus && setIsLoop(false) return } const arr = [...data, ...newData] @@ -1040,7 +1048,7 @@ export const HTTPFlowTable = React.memo((props) => { } else if (type === "offset") { if (resData.length <= 0) { // 没有数据 - serverPushStatus&&setIsLoop(false) + serverPushStatus && setIsLoop(false) return } if (["desc", "none"].includes(query.Pagination.Order)) { @@ -1052,7 +1060,7 @@ export const HTTPFlowTable = React.memo((props) => { // if (rsp?.Data.length > 0 && data.length > 0 && rsp?.Data[0].Id === data[0].Id) return if (resData.length <= 0) { // 没有数据 - serverPushStatus&&setIsLoop(false) + serverPushStatus && setIsLoop(false) } setSelectedRowKeys([]) setSelectedRows([]) @@ -1086,10 +1094,10 @@ export const HTTPFlowTable = React.memo((props) => { }) const getAddDataByGrpc = useMemoizedFn((query) => { - if(!isLoop) return + if (!isLoop) return const clientHeight = tableRef.current?.containerRef?.clientHeight // 解决页面未显示时 此接口轮询导致接口锁死 - if(clientHeight === 0) return + if (clientHeight === 0) return const copyQuery = structuredClone(query) copyQuery.Pagination = { Page: 1, @@ -1127,18 +1135,8 @@ export const HTTPFlowTable = React.memo((props) => { const query = { ...params, - Tags: params.Tags, Pagination: {...paginationProps}, - AfterId: maxIdRef.current, - AfterBodyLength: params.AfterBodyLength - ? onConvertBodySizeByUnit(params.AfterBodyLength, getBodyLengthUnit()) - : undefined, - BeforeBodyLength: params.BeforeBodyLength - ? onConvertBodySizeByUnit(params.BeforeBodyLength, getBodyLengthUnit()) - : undefined - } - if (checkBodyLength && !query.AfterBodyLength) { - query.AfterBodyLength = 1 + AfterId: maxIdRef.current } if (pageType === "MITM" && query.AfterUpdatedAt === undefined && query.BeforeUpdatedAt === undefined) { updateMITMPageQuery(query, "top") @@ -1163,19 +1161,9 @@ export const HTTPFlowTable = React.memo((props) => { const query = { ...params, - Tags: params.Tags, BeforeId: ["desc", "none"].includes(paginationProps.Order) ? minIdRef.current : undefined, AfterId: ["desc", "none"].includes(paginationProps.Order) ? undefined : maxIdRef.current, - Pagination: {...paginationProps}, - AfterBodyLength: params.AfterBodyLength - ? onConvertBodySizeByUnit(params.AfterBodyLength, getBodyLengthUnit()) - : undefined, - BeforeBodyLength: params.BeforeBodyLength - ? onConvertBodySizeByUnit(params.BeforeBodyLength, getBodyLengthUnit()) - : undefined - } - if (checkBodyLength && !query.AfterBodyLength) { - query.AfterBodyLength = 1 + Pagination: {...paginationProps} } if (pageType === "MITM" && query.AfterUpdatedAt === undefined && query.BeforeUpdatedAt === undefined) { updateMITMPageQuery(query, "bottom") @@ -1199,25 +1187,19 @@ export const HTTPFlowTable = React.memo((props) => { } const query = { ...params, - Tags: params.Tags, - Pagination: {...paginationProps}, - AfterBodyLength: params.AfterBodyLength - ? onConvertBodySizeByUnit(params.AfterBodyLength, getBodyLengthUnit()) - : undefined, - BeforeBodyLength: params.BeforeBodyLength - ? onConvertBodySizeByUnit(params.BeforeBodyLength, getBodyLengthUnit()) - : undefined - } - if (checkBodyLength && !query.AfterBodyLength) { - query.AfterBodyLength = 1 + Pagination: {...paginationProps} } if (pageType === "MITM" && query.AfterUpdatedAt === undefined && query.BeforeUpdatedAt === undefined) { updateMITMPageQuery(query, "update") return } setIsRefresh(!isRefresh) + setSelectedRowKeys([]) + setSelectedRows([]) + setScrollToIndex(0) + setCurrentIndex(undefined) getDataByGrpc(query, "update") - } else{ + } else { setIsLoop(true) } }) @@ -1232,22 +1214,10 @@ export const HTTPFlowTable = React.memo((props) => { } const query = { ...params, - Tags: params.Tags, - Color: color ? color : undefined, - AfterBodyLength: params.AfterBodyLength - ? onConvertBodySizeByUnit(params.AfterBodyLength, getBodyLengthUnit()) - : undefined, - BeforeBodyLength: params.BeforeBodyLength - ? onConvertBodySizeByUnit(params.BeforeBodyLength, getBodyLengthUnit()) - : undefined, AfterId: maxIdRef.current, Pagination: {...paginationProps} } - if (props.params?.SourceType) { - query.SourceType = props.params?.SourceType - } - if (pageType === "MITM" && query.AfterUpdatedAt === undefined && query.BeforeUpdatedAt === undefined) { updateMITMPageQuery(query, "offset") return @@ -1284,7 +1254,7 @@ export const HTTPFlowTable = React.memo((props) => { RefreshRequest }) .then((rsp: HTTPFlowsFieldGroupResponse) => { - const tags = rsp.Tags + const tags = rsp.Tags.filter((item) => (toWebFuzzer ? item.Value === "webfuzzer" : item.Value)) setTags(tags.map((ele) => ({label: ele.Value, value: ele.Value}))) }) .catch((e: any) => { @@ -1337,25 +1307,25 @@ export const HTTPFlowTable = React.memo((props) => { }, [selected]) // 是否循环接口 - const [isLoop,setIsLoop] = useState(!serverPushStatus) + const [isLoop, setIsLoop] = useState(!serverPushStatus) - const onRefreshQueryHTTPFlowsFun = useMemoizedFn(()=>{ + const onRefreshQueryHTTPFlowsFun = useMemoizedFn(() => { setIsLoop(true) }) - useEffect(()=>{ + useEffect(() => { emiter.on("onRefreshQueryHTTPFlows", onRefreshQueryHTTPFlowsFun) return () => { emiter.off("onRefreshQueryHTTPFlows", onRefreshQueryHTTPFlowsFun) } - },[]) + }, []) - useEffect(()=>{ - let sTop,cHeight,sHeight - let id = setInterval(()=>{ + useEffect(() => { + let sTop, cHeight, sHeight + let id = setInterval(() => { const scrollTop = tableRef.current?.containerRef?.scrollTop const clientHeight = tableRef.current?.containerRef?.clientHeight const scrollHeight = tableRef.current?.containerRef?.scrollHeight - if(sTop!==scrollTop||cHeight!==clientHeight||sHeight!==scrollHeight){ + if (sTop !== scrollTop || cHeight !== clientHeight || sHeight !== scrollHeight) { setIsLoop(true) } sTop = scrollTop @@ -1363,7 +1333,7 @@ export const HTTPFlowTable = React.memo((props) => { sHeight = scrollHeight }, 1000) return () => clearInterval(id) - },[]) + }, []) // 设置是否自动刷新 useEffect(() => { @@ -1375,7 +1345,7 @@ export const HTTPFlowTable = React.memo((props) => { } } return () => clearInterval(id) - }, [inViewport,isLoop,scrollUpdate]) + }, [inViewport, isLoop, scrollUpdate]) // 保留数组中非重复数据 const filterNonUnique = (arr) => arr.filter((i) => arr.indexOf(i) === arr.lastIndexOf(i)) @@ -1469,6 +1439,10 @@ export const HTTPFlowTable = React.memo((props) => { const onCheckThan0 = useDebounceFn( (check: boolean) => { setCheckBodyLength(check) + if (!getAfterBodyLength()) { + params.AfterBodyLength = check ? 1 : undefined + } + setParams(params) setTimeout(() => { updateData() }, 100) @@ -1476,381 +1450,431 @@ export const HTTPFlowTable = React.memo((props) => { {wait: 200} ).run const columns: ColumnsTypeProps[] = useMemo(() => { - return [ - { - title: "序号", - dataKey: "Id", - fixed: "left", - ellipsis: false, - width: 96, - enableDrag: false, - sorterProps: { - sorter: true - } - }, - { - title: "方法", - dataKey: "Method", - width: 80, - filterProps: { - filterKey: "Methods", - filtersType: "select", - filtersSelectAll: { - isAll: true + const ID: ColumnsTypeProps = { + title: "序号", + dataKey: "Id", + fixed: "left", + ellipsis: false, + width: 96, + enableDrag: false, + sorterProps: { + sorter: true + } + } + const Method: ColumnsTypeProps = { + title: "方法", + dataKey: "Method", + width: 80, + filterProps: { + filterKey: "Methods", + filtersType: "select", + filtersSelectAll: { + isAll: true + }, + filters: [ + { + label: "GET", + value: "GET" }, - filters: [ - { - label: "GET", - value: "GET" - }, - { - label: "POST", - value: "POST" - }, - { - label: "HEAD", - value: "HEAD" - }, - { - label: "PUT", - value: "PUT" - }, - { - label: "DELETE", - value: "DELETE" - } - ] - } - }, - { - title: "状态码", - dataKey: "StatusCode", - width: 100, - filterProps: { - filterMultiple: true, - filtersType: "select", - filterSearchInputProps: { - size: "small" + { + label: "POST", + value: "POST" }, - filterOptionRender: (item: FiltersItemProps) => ( -
- {item.value} - {item.total} -
- ), - filters: [ - { - value: "100-199", - label: "[100,200)" - }, - { - value: "200-299", - label: "[200-300)" - }, - { - value: "300-399", - label: "[300-400)" - }, - { - value: "400-499", - label: "[400-500)" - }, - { - value: "500-600", - label: "[500-600]" - } - ] - }, - render: (text, rowData) => { - return ( -
- {text} -
- ) - } - }, - { - title: "URL", - dataKey: "Url", - width: 400, - filterProps: { - filterKey: "SearchURL", - filtersType: "input", - filterIcon: ( - - ), - } - }, - { - title: "Title", - dataKey: "HtmlTitle" - }, - { - title: "Tags", - dataKey: "Tags", - width: 150, - render: (text) => { - return text - ? `${text}` - .split("|") - .filter((i) => !i.startsWith("YAKIT_COLOR_")) - .join(", ") - : "" + { + label: "HEAD", + value: "HEAD" + }, + { + label: "PUT", + value: "PUT" + }, + { + label: "DELETE", + value: "DELETE" + } + ] + } + } + const StatusCode: ColumnsTypeProps = { + title: "状态码", + dataKey: "StatusCode", + width: 100, + filterProps: { + filterMultiple: true, + filtersType: "select", + filterSearchInputProps: { + size: "small" }, - filterProps: { - filterKey: "Tags", - filterMultiple: true, - filterIcon: ( - getHTTPFlowsFieldGroup(true)} - /> - ), - filterRender: (closePopover: () => void) => { - return ( - , - allowClear: true - } - }} - originalList={tags} - value={tagsFilter} - onSelect={(v, item) => { - if (Array.isArray(v)) { - setTagsFilter(v) - } - }} - onClose={() => { - closePopover() - }} - onQuery={() => { - // 这里重置过后的 tagsFilter 不一定是最新的 - setParams({ - ...getParams(), - Tags: [] - }) - setTimeout(() => { - updateData() - }, 100) - }} - > - ) + filterOptionRender: (item: FiltersItemProps) => ( +
+ {item.value} + {item.total} +
+ ), + filters: [ + { + value: "100-199", + label: "[100,200)" + }, + { + value: "200-299", + label: "[200-300)" + }, + { + value: "300-399", + label: "[300-400)" + }, + { + value: "400-499", + label: "[400-500)" + }, + { + value: "500-600", + label: "[500-600]" } - } + ] }, - { - title: "IP", - dataKey: "IPAddress", - width: 200 - }, - { - title: "响应长度", - dataKey: "BodyLength", - width: 200, - minWidth: 140, - beforeIconExtra: ( -
- onCheckThan0(e.target.checked)} /> - 大于0 + render: (text, rowData) => { + return ( +
+ {text}
+ ) + } + } + const Url: ColumnsTypeProps = { + title: "URL", + dataKey: "Url", + width: 400, + filterProps: { + filterKey: "SearchURL", + filtersType: "input", + filterIcon: + } + } + const HtmlTitle: ColumnsTypeProps = { + title: "Title", + dataKey: "HtmlTitle", + width: 200 + } + const WebPayloads: ColumnsTypeProps = { + title: "Payloads", + dataKey: "Payloads", + width: 300, + render: (v) => { + return v ? v.join(",") : "-" + } + } + const Tags: ColumnsTypeProps = { + title: "Tags", + dataKey: "Tags", + width: 150, + render: (text) => { + return text + ? `${text}` + .split("|") + .filter((i) => !i.startsWith("YAKIT_COLOR_")) + .join(", ") + : "" + }, + filterProps: { + filterKey: "Tags", + filterMultiple: true, + filterIcon: ( + getHTTPFlowsFieldGroup(true)} /> ), - filterProps: { - filterKey: "bodyLength", - filterRender: () => ( - { - setParams({ - ...getParams(), - AfterBodyLength: getAfterBodyLength(), - BeforeBodyLength: getBeforeBodyLength() - }) - setBeforeBodyLength(undefined) - setAfterBodyLength(undefined) - setBodyLengthUnit("B") + filterRender: (closePopover: () => void) => { + return ( + , + allowClear: true + } + }} + originalList={tags} + value={tagsFilter} + onSelect={(v, item) => { + if (Array.isArray(v)) { + setTagsFilter(v) + } }} - onSure={() => { + onClose={() => { + closePopover() + }} + onQuery={() => { + // 这里重置过后的 tagsFilter 不一定是最新的 setParams({ ...getParams(), - AfterBodyLength: getAfterBodyLength(), - BeforeBodyLength: getBeforeBodyLength() + Tags: [] }) setTimeout(() => { updateData() }, 100) }} - extra={ - { - setBodyLengthUnit(val) - }} - wrapperClassName={style["unit-select"]} - size='small' - > - B - K - M - - } - /> - ) - }, - render: (_, rowData) => { - return ( - <> - {/* 1M 以上的话,是红色*/} - {rowData.BodyLength !== -1 && ( -
1000000 && !hasRedOpacityBg(rowData.cellClassName) - })} - > - {rowData.BodySizeVerbose ? rowData.BodySizeVerbose : rowData.BodyLength} -
- )} - + >
) } - }, - { - title: "参数", - dataKey: "GetParamsTotal", - filterProps: { - filterKey: "HaveParamsTotal", - filtersType: "select", - filtersSelectAll: { - isAll: true - }, - filters: [ - { - label: "有", - value: "true" - }, - { - label: "无", - value: "false" + } + } + const IPAddress: ColumnsTypeProps = { + title: "IP", + dataKey: "IPAddress", + width: 200 + } + const BodyLength: ColumnsTypeProps = { + title: "响应长度", + dataKey: "BodyLength", + width: 200, + minWidth: 140, + beforeIconExtra: ( +
+ onCheckThan0(e.target.checked)} /> + 大于0 +
+ ), + filterProps: { + filterKey: "bodyLength", + filterRender: () => ( + { + setParams({ + ...getParams(), + AfterBodyLength: checkBodyLength ? 1 : undefined, + BeforeBodyLength: undefined + }) + setBeforeBodyLength(undefined) + setAfterBodyLength(undefined) + setBodyLengthUnit("B") + }} + onSure={() => { + const afterBodyLen = getAfterBodyLength() + const beforeBodyLen = getBeforeBodyLength() + + setParams({ + ...getParams(), + AfterBodyLength: + checkBodyLength && !afterBodyLen + ? 1 + : afterBodyLen + ? onConvertBodySizeByUnit(afterBodyLen, getBodyLengthUnit()) + : undefined, + BeforeBodyLength: beforeBodyLen + ? onConvertBodySizeByUnit(beforeBodyLen, getBodyLengthUnit()) + : undefined + }) + setTimeout(() => { + updateData() + }, 100) + }} + extra={ + { + setBodyLengthUnit(val) + }} + wrapperClassName={style["unit-select"]} + size='small' + > + B + K + M + } - ] - }, - render: (_, rowData) => ( -
- {(rowData.GetParamsTotal > 0 || rowData.PostParamsTotal > 0) && ( - + ) + }, + render: (_, rowData) => { + return ( + <> + {/* 1M 以上的话,是红色*/} + {rowData.BodyLength !== -1 && ( +
1000000 && !hasRedOpacityBg(rowData.cellClassName) })} - /> + > + {rowData.BodySizeVerbose ? rowData.BodySizeVerbose : rowData.BodyLength} +
)} -
+ ) - }, - { - title: "响应类型", - dataKey: "ContentType", - render: (text) => { - let contentTypeFixed = - text - .split(";") - .map((el: any) => el.trim()) - .filter((i: any) => !i.startsWith("charset")) - .join(",") || "-" - if (contentTypeFixed.includes("/")) { - const contentTypeFixedNew = contentTypeFixed.split("/").pop() - if (!!contentTypeFixedNew) { - contentTypeFixed = contentTypeFixedNew - } - } - return
{contentTypeFixed === "null" ? "" : contentTypeFixed}
+ } + } + const GetParamsTotal: ColumnsTypeProps = { + title: "参数", + dataKey: "GetParamsTotal", + width: 100, + filterProps: { + filterKey: "HaveParamsTotal", + filtersType: "select", + filtersSelectAll: { + isAll: true }, - filterProps: { - filtersType: "select", - filterMultiple: true, - filterSearchInputProps: { - size: "small" + filters: [ + { + label: "有", + value: "true" }, - filterIcon: , - filters: contentType + { + label: "无", + value: "false" + } + ] + }, + render: (_, rowData) => ( +
+ {(rowData.GetParamsTotal > 0 || rowData.PostParamsTotal > 0) && ( + + )} +
+ ) + } + const ContentType: ColumnsTypeProps = { + title: "响应类型", + dataKey: "ContentType", + width: 150, + render: (text) => { + let contentTypeFixed = + text + .split(";") + .map((el: any) => el.trim()) + .filter((i: any) => !i.startsWith("charset")) + .join(",") || "-" + if (contentTypeFixed.includes("/")) { + const contentTypeFixedNew = contentTypeFixed.split("/").pop() + if (!!contentTypeFixedNew) { + contentTypeFixed = contentTypeFixedNew + } } + return
{contentTypeFixed === "null" ? "" : contentTypeFixed}
}, - { - title: "请求时间", - dataKey: "UpdatedAt", - // sorterProps: { - // sorterKey: "updated_at", - // sorter: true - // }, - filterProps: { - filterKey: "UpdatedAt", - filtersType: "dateTime" + filterProps: { + filtersType: "select", + filterMultiple: true, + filterSearchInputProps: { + size: "small" }, - // fixed: "right", - render: (text) =>
{text === 0 ? "-" : formatTime(text)}
- }, - { - title: "请求大小", - dataKey: "RequestSizeVerbose", - // fixed: "right", - enableDrag: false + filterIcon: , + filters: contentType + } + } + const UpdatedAt: ColumnsTypeProps = { + title: "请求时间", + dataKey: "UpdatedAt", + // sorterProps: { + // sorterKey: "updated_at", + // sorter: true + // }, + filterProps: { + filterKey: "UpdatedAt", + filtersType: "dateTime" }, - { - title: "操作", - dataKey: "action", - width: 80, - fixed: "right", - render: (_, rowData) => { - if (!rowData.Hash) return <> - return ( -
- { - e.stopPropagation() - ipcRenderer - .invoke("GetHTTPFlowById", {Id: rowData?.Id}) - .then((i: HTTPFlow) => { - showResponseViaResponseRaw(i?.Response) - }) - .catch((e: any) => { - yakitNotify("error", `Query HTTPFlow failed: ${e}`) - }) - }} - /> -
- - { - e.stopPropagation() - let m = showDrawer({ - width: "80%", - content: onExpandHTTPFlow(rowData, () => m.destroy()) + // fixed: "right", + width: 200, + render: (text) =>
{text === 0 ? "-" : formatTime(text)}
+ } + const RequestSizeVerbose: ColumnsTypeProps = { + title: "请求大小", + dataKey: "RequestSizeVerbose", + // fixed: "right", + enableDrag: false, + width: 200, + } + const action: ColumnsTypeProps = { + title: "操作", + dataKey: "action", + width: 80, + fixed: "right", + render: (_, rowData) => { + if (!rowData.Hash) return <> + return ( +
+ { + e.stopPropagation() + ipcRenderer + .invoke("GetHTTPFlowById", {Id: rowData?.Id}) + .then((i: HTTPFlow) => { + showResponseViaResponseRaw(i?.Response) }) - }} - /> -
- ) - } + .catch((e: any) => { + yakitNotify("error", `Query HTTPFlow failed: ${e}`) + }) + }} + /> +
+ + { + e.stopPropagation() + let m = showDrawer({ + width: "80%", + content: onExpandHTTPFlow(rowData, () => m.destroy()) + }) + }} + /> +
+ ) } + } + + // toWebFuzzer + if (toWebFuzzer) { + return [ + ID, + Method, + StatusCode, + Url, + HtmlTitle, + WebPayloads, + Tags, + IPAddress, + BodyLength, + GetParamsTotal, + ContentType, + UpdatedAt, + RequestSizeVerbose, + action + ] + } + + return [ + ID, + Method, + StatusCode, + Url, + HtmlTitle, + Tags, + IPAddress, + BodyLength, + GetParamsTotal, + ContentType, + UpdatedAt, + RequestSizeVerbose, + action ] - }, [tags, tagsFilter, checkBodyLength]) + }, [tags, tagsFilter, checkBodyLength, toWebFuzzer]) // 背景颜色是否标注为红色 const hasRedOpacityBg = (cellClassName: string) => cellClassName.indexOf("color-opacity-bg-red") !== -1 @@ -2021,7 +2045,10 @@ export const HTTPFlowTable = React.memo((props) => { ...(props.params || {SourceType: "mitm"}), SourceType: props.params?.SourceType || "mitm", ExcludeId: params.ExcludeId, - ExcludeInUrl: params.ExcludeInUrl + ExcludeInUrl: params.ExcludeInUrl, + WithPayload: toWebFuzzer, + RuntimeIDs: toWebFuzzer && runTimeId ? runTimeId.split(",") : undefined, + RuntimeId: !toWebFuzzer ? runTimeId || undefined : undefined } setParams({...newParams}) updateData() @@ -2121,25 +2148,15 @@ export const HTTPFlowTable = React.memo((props) => { const l = data.length const query: any = { ...params, - Tags: params.Tags, Pagination: {...pagination}, // OffsetId: // pagination.Page === 1 // ? undefined // : data[l - 1] && data[l - 1].Id && (Math.ceil(data[l - 1].Id) as number), OffsetId: undefined, - AfterBodyLength: params.AfterBodyLength - ? onConvertBodySizeByUnit(params.AfterBodyLength, getBodyLengthUnit()) - : undefined, - BeforeBodyLength: params.BeforeBodyLength - ? onConvertBodySizeByUnit(params.BeforeBodyLength, getBodyLengthUnit()) - : undefined, Full: false } - if (checkBodyLength && !query.AfterBodyLength) { - query.AfterBodyLength = 1 - } let exportParams: any = {} // 这里的key值 不一定和表格的key对应的上 const arrList = [ @@ -2163,6 +2180,10 @@ export const HTTPFlowTable = React.memo((props) => { title: "Title", key: "response" }, + { + title: "Payloads", + key: "payloads" + }, { title: "Tags", key: "tags" @@ -2261,9 +2282,12 @@ export const HTTPFlowTable = React.memo((props) => { content: ( !["参数", "请求包", "响应包"].includes(i)) : exportValue + } setExportTitle={(v: string[]) => setExportTitle(["序号", ...v])} - exportKey='MITM-HTTP-HISTORY-EXPORT-KEY' - fileName='History' + exportKey={toWebFuzzer ? "WEBFUZZER-HISTORY-EXPORT-KEY" : "MITM-HTTP-HISTORY-EXPORT-KEY"} + fileName={!toWebFuzzer ? "History" : "WebFuzzer"} getData={(pagination) => getExcelData(pagination, list)} /> ), @@ -2291,6 +2315,8 @@ export const HTTPFlowTable = React.memo((props) => { label: "发送到 Web Fuzzer", number: 10, default: true, + webSocket: false, + toWebFuzzer: true, children: [ { key: "sendAndJumpToWebFuzzer", @@ -2306,11 +2332,12 @@ export const HTTPFlowTable = React.memo((props) => { onClickBatch: () => {} }, { - key: "发送到WS Fuzzer", - label: "发送到WS Fuzzer", + key: "发送到 WS Fuzzer", + label: "发送到 WS Fuzzer", number: 10, webSocket: true, default: false, + toWebFuzzer: false, children: [ { key: "sendAndJumpToWS", @@ -2330,6 +2357,8 @@ export const HTTPFlowTable = React.memo((props) => { label: "数据包扫描", number: 10, default: true, + webSocket: false, + toWebFuzzer: true, onClickSingle: () => {}, onClickBatch: () => {}, children: GetPacketScanByCursorMenuItem(selected?.Id || 0)?.subMenuItems?.map((ele) => ({ @@ -2343,6 +2372,7 @@ export const HTTPFlowTable = React.memo((props) => { number: 30, webSocket: true, default: true, + toWebFuzzer: true, onClickSingle: (v) => callCopyToClipboard(v.Url), onClickBatch: (v, number) => { if (v.length === 0) { @@ -2362,6 +2392,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "下载 Response Body", label: "下载 Response Body", default: true, + webSocket: false, + toWebFuzzer: true, onClickSingle: (v) => { ipcRenderer.invoke("GetResponseBodyByHTTPFlowID", {Id: v.Id}).then((bytes: {Raw: Uint8Array}) => { saveABSFileToOpen(`response-body.txt`, bytes.Raw) @@ -2372,6 +2404,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "浏览器中打开", label: "浏览器中打开", default: true, + webSocket: false, + toWebFuzzer: true, onClickSingle: (v) => { showResponseViaHTTPFlowID(v) } @@ -2380,6 +2414,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "复制为 CSRF Poc", label: "复制为 CSRF Poc", default: true, + webSocket: false, + toWebFuzzer: true, onClickSingle: (v) => { const flow = v as HTTPFlow if (!flow) return @@ -2392,6 +2428,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "复制为 Yak PoC 模版", label: "复制为 Yak PoC 模版", default: true, + webSocket: false, + toWebFuzzer: true, onClickSingle: () => {}, children: [ { @@ -2408,6 +2446,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "标注颜色", label: "标注颜色", default: true, + webSocket: false, + toWebFuzzer: true, number: 20, onClickSingle: () => {}, onClickBatch: () => {}, @@ -2424,6 +2464,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "移除颜色", label: "移除颜色", default: true, + webSocket: false, + toWebFuzzer: true, number: 20, onClickSingle: (v) => onRemoveCalloutColor(v, data, setData), onClickBatch: (list, n) => onRemoveCalloutColorBatch(list, n) @@ -2432,6 +2474,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "发送到对比器", label: "发送到对比器", default: true, + webSocket: false, + toWebFuzzer: true, onClickSingle: () => {}, children: [ { @@ -2451,6 +2495,7 @@ export const HTTPFlowTable = React.memo((props) => { label: "屏蔽", webSocket: true, default: true, + toWebFuzzer: true, onClickSingle: () => {}, children: [ { @@ -2472,6 +2517,7 @@ export const HTTPFlowTable = React.memo((props) => { label: "删除", webSocket: true, default: true, + toWebFuzzer: true, onClickSingle: () => {}, onClickBatch: () => {}, all: true, @@ -2517,6 +2563,8 @@ export const HTTPFlowTable = React.memo((props) => { label: "分享数据包", number: 30, default: true, + webSocket: false, + toWebFuzzer: false, onClickSingle: (v) => onShareData([v.Id], 50), onClickBatch: (list, n) => { const ids: string[] = list.map((ele) => ele.Id) @@ -2527,6 +2575,8 @@ export const HTTPFlowTable = React.memo((props) => { key: "导出数据", label: "导出数据", default: true, + webSocket: false, + toWebFuzzer: true, onClickSingle: (v) => onExcelExport([v]), onClickBatch: (list, n) => onExcelExport(list) } @@ -2565,7 +2615,9 @@ export const HTTPFlowTable = React.memo((props) => { { width: 180, data: contextMenuKeybindingHandle(menuData) - .filter((item) => (rowData.IsWebsocket ? item.webSocket : item.default)) + .filter((item) => + rowData.IsWebsocket ? item.webSocket : toWebFuzzer ? item.toWebFuzzer : item.default + ) .map((ele) => { return { label: ele.label, @@ -2720,8 +2772,9 @@ export const HTTPFlowTable = React.memo((props) => { SourceType: props.params?.SourceType || "mitm", ExcludeId: params.ExcludeId, ExcludeInUrl: params.ExcludeInUrl, - SearchURL: "", - IncludeInUrl: [] + WithPayload: toWebFuzzer, + RuntimeIDs: toWebFuzzer && runTimeId ? runTimeId.split(",") : undefined, + RuntimeId: !toWebFuzzer ? runTimeId || undefined : undefined } setParams(newParams) setIsReset(!isReset) @@ -2786,8 +2839,214 @@ export const HTTPFlowTable = React.memo((props) => { ) }) + const searchEle = useMemoizedFn(() => { + return ( + <> + {size?.width && size?.width < 1000 ? ( + + } type='outline2' /> + + ) : ( + { + setParams({...params, Keyword: e.target.value}) + }} + onSearch={() => { + setTimeout(() => { + updateData() + }, 50) + }} + // 这个事件很关键哈,不要用 onChange + onBlur={(e) => { + if (props.onSearch) { + props.onSearch(e.target.value) + } + }} + /> + )} + + ) + }) + + const batchActions = useMemoizedFn(() => { + return ( + <> + {size?.width && size?.width >= 1000 && ( + <> + {(selectedRowKeys.length === 0 && ( + { + e.stopPropagation() + }} + > + 批量操作 + + + )) || ( + + toWebFuzzer ? f.onClickBatch && f.toWebFuzzer : f.onClickBatch + ) + .map((m) => { + return { + key: m.key, + label: m.label, + children: m.children?.map((ele) => ({ + key: ele.key, + label: ele.label + })) + } + })} + onClick={({key, keyPath}) => { + if (keyPath.includes("数据包扫描")) { + if (isAllSelect) { + yakitNotify("warning", "该批量操作不支持全选") + return + } + const currentItemScan = menuData.find( + (f) => f.onClickBatch && f.key === "数据包扫描" + ) + const currentItemPacketScan = packetScanDefaultValue.find( + (f) => f.Verbose === key + ) + if (!currentItemScan || !currentItemPacketScan) return + + onBatchExecPacketScan({ + httpFlowIds: selectedRowKeys, + maxLength: currentItemScan.number || 0, + currentPacketScan: currentItemPacketScan + }) + return + } + if (keyPath.includes("标注颜色")) { + const currentItemColor = menuData.find( + (f) => f.onClickBatch && f.key === "标注颜色" + ) + const colorItem = availableColors.find((e) => e.title === key) + if (!currentItemColor || !colorItem) return + CalloutColorBatch( + selectedRows, + currentItemColor?.number || 0, + colorItem + ) + return + } + switch (key) { + case "删除记录": + onRemoveHttpHistory({ + Id: selectedRowKeys + }) + break + case "删除URL": + const urls = selectedRows.map((ele) => ele.Url) + onRemoveHttpHistory({ + Filter: { + IncludeInUrl: urls + } + }) + break + case "删除域名": + const hosts = selectedRows.map((ele) => ele.HostPort?.split(":")[0]) + onRemoveHttpHistory({ + Filter: { + IncludeInUrl: hosts + } + }) + break + case "sendAndJumpToWebFuzzer": + const currentItemJumpToFuzzer = menuData.find( + (f) => f.onClickBatch && f.key === "发送到 Web Fuzzer" + ) + if (!currentItemJumpToFuzzer) return + onBatch( + onSendToTab, + currentItemJumpToFuzzer?.number || 0, + selectedRowKeys.length === total + ) + + break + case "sendToWebFuzzer": + const currentItemToFuzzer = menuData.find( + (f) => f.onClickBatch && f.key === "发送到 Web Fuzzer" + ) + if (!currentItemToFuzzer) return + onBatch( + (el) => onSendToTab(el, false), + currentItemToFuzzer?.number || 0, + selectedRowKeys.length === total + ) + break + case "sendAndJumpToWS": + const currentItemJumpToWS = menuData.find( + (f) => f.onClickBatch && f.key === "发送到WS Fuzzer" + ) + if (!currentItemJumpToWS) return + onBatch( + (el) => newWebsocketFuzzerTab(el.IsHTTPS, el.Request), + currentItemJumpToWS?.number || 0, + selectedRowKeys.length === total + ) + + break + case "sendToWS": + const currentItemToWS = menuData.find( + (f) => f.onClickBatch && f.key === "发送到WS Fuzzer" + ) + if (!currentItemToWS) return + onBatch( + (el) => newWebsocketFuzzerTab(el.IsHTTPS, el.Request, false), + currentItemToWS?.number || 0, + selectedRowKeys.length === total + ) + break + default: + const currentItem = menuData.find( + (f) => f.onClickBatch && f.key === key + ) + if (!currentItem) return + if (currentItem.onClickBatch) + currentItem.onClickBatch(selectedRows, currentItem.number) + break + } + setBatchVisible(false) + }} + /> + } + trigger='click' + placement='bottomLeft' + onVisibleChange={setBatchVisible} + visible={batchVisible} + > + { + e.stopPropagation() + }} + > + 批量操作 + + + + )} + + )} + + ) + }) + return ( - //
} tabIndex={-1} style={{width: "100%", height: "100%", overflow: "hidden"}}> { @@ -2884,7 +3143,12 @@ export const HTTPFlowTable = React.memo((props) => {
- {!onlyShowSearch && ( + {onlyShowSearch ? ( + <> + {searchEle()} + {showBatchActions &&
{batchActions()}
} + + ) : ( <> ((props) => { websocket
- - )} - {size?.width && size?.width < 1000 ? ( - - } type='outline2' /> - - ) : ( - { - setParams({...params, Keyword: e.target.value}) - }} - onSearch={() => { - setTimeout(() => { - updateData() - }, 50) - }} - // 这个事件很关键哈,不要用 onChange - onBlur={(e) => { - if (props.onSearch) { - props.onSearch(e.target.value) - } - }} - /> - )} - - {!onlyShowSearch && ( - <> + {searchEle()}
((props) => {
- {size?.width && size?.width >= 1000 && ( - <> - {(selectedRowKeys.length === 0 && ( - { - e.stopPropagation() - }} - > - 批量操作 - - - )) || ( - f.onClickBatch) - .map((m) => { - return { - key: m.key, - label: m.label, - children: m.children?.map((ele) => ({ - key: ele.key, - label: ele.label - })) - } - })} - onClick={({key, keyPath}) => { - if (keyPath.includes("数据包扫描")) { - if (isAllSelect) { - yakitNotify( - "warning", - "该批量操作不支持全选" - ) - return - } - const currentItemScan = menuData.find( - (f) => - f.onClickBatch && - f.key === "数据包扫描" - ) - const currentItemPacketScan = - packetScanDefaultValue.find( - (f) => f.Verbose === key - ) - if ( - !currentItemScan || - !currentItemPacketScan - ) - return - - onBatchExecPacketScan({ - httpFlowIds: selectedRowKeys, - maxLength: currentItemScan.number || 0, - currentPacketScan: currentItemPacketScan - }) - return - } - if (keyPath.includes("标注颜色")) { - const currentItemColor = menuData.find( - (f) => - f.onClickBatch && - f.key === "标注颜色" - ) - const colorItem = availableColors.find( - (e) => e.title === key - ) - if (!currentItemColor || !colorItem) return - CalloutColorBatch( - selectedRows, - currentItemColor?.number || 0, - colorItem - ) - return - } - switch (key) { - case "删除记录": - onRemoveHttpHistory({ - Id: selectedRowKeys - }) - break - case "删除URL": - const urls = selectedRows.map( - (ele) => ele.Url - ) - onRemoveHttpHistory({ - Filter: { - IncludeInUrl: urls - } - }) - break - case "删除域名": - const hosts = selectedRows.map( - (ele) => ele.HostPort?.split(":")[0] - ) - onRemoveHttpHistory({ - Filter: { - IncludeInUrl: hosts - } - }) - break - case "sendAndJumpToWebFuzzer": - const currentItemJumpToFuzzer = - menuData.find( - (f) => - f.onClickBatch && - f.key === - "发送到 Web Fuzzer" - ) - if (!currentItemJumpToFuzzer) return - onBatch( - onSendToTab, - currentItemJumpToFuzzer?.number || - 0, - selectedRowKeys.length === total - ) - - break - case "sendToWebFuzzer": - const currentItemToFuzzer = - menuData.find( - (f) => - f.onClickBatch && - f.key === - "发送到 Web Fuzzer" - ) - if (!currentItemToFuzzer) return - onBatch( - (el) => onSendToTab(el, false), - currentItemToFuzzer?.number || 0, - selectedRowKeys.length === total - ) - break - case "sendAndJumpToWS": - const currentItemJumpToWS = - menuData.find( - (f) => - f.onClickBatch && - f.key === "发送到WS Fuzzer" - ) - if (!currentItemJumpToWS) return - onBatch( - (el) => - newWebsocketFuzzerTab( - el.IsHTTPS, - el.Request - ), - currentItemJumpToWS?.number || 0, - selectedRowKeys.length === total - ) - - break - case "sendToWS": - const currentItemToWS = menuData.find( - (f) => - f.onClickBatch && - f.key === "发送到WS Fuzzer" - ) - if (!currentItemToWS) return - onBatch( - (el) => - newWebsocketFuzzerTab( - el.IsHTTPS, - el.Request, - false - ), - currentItemToWS?.number || 0, - selectedRowKeys.length === total - ) - break - default: - const currentItem = menuData.find( - (f) => - f.onClickBatch && f.key === key - ) - if (!currentItem) return - if (currentItem.onClickBatch) - currentItem.onClickBatch( - selectedRows, - currentItem.number - ) - break - } - setBatchVisible(false) - }} - /> - } - trigger='click' - placement='bottomLeft' - onVisibleChange={setBatchVisible} - visible={batchVisible} - > - { - e.stopPropagation() - }} - > - 批量操作 - - - - )} - - )} + {showBatchActions && batchActions()} )}
@@ -3336,7 +3359,6 @@ export const HTTPFlowTable = React.memo((props) => { searchContentType={searchContentType} />
- // ) }) @@ -3725,7 +3747,8 @@ export const CalloutColor = (flow: HTTPFlow, i: any, data: HTTPFlow[], setData) newData.push(item) } setData(newData) - }).catch((e) => { + }) + .catch((e) => { yakitFailed(e + "") }) } diff --git a/app/renderer/src/main/src/components/TableVirtualResize/TableVirtualResize.tsx b/app/renderer/src/main/src/components/TableVirtualResize/TableVirtualResize.tsx index adf45a65db..a84876673f 100644 --- a/app/renderer/src/main/src/components/TableVirtualResize/TableVirtualResize.tsx +++ b/app/renderer/src/main/src/components/TableVirtualResize/TableVirtualResize.tsx @@ -234,7 +234,6 @@ const Table = (props: TableVirtualResizeProps) => { useHotkeys( "up", () => { - if (!useUpAndDown) return if (!setCurrentRow) return const dataLength = data.length if (dataLength <= 0) { @@ -268,7 +267,7 @@ const Table = (props: TableVirtualResizeProps) => { }, 50) } }, - {enabled: inViewport}, + {enabled: inViewport && useUpAndDown}, [data, currentRow, containerRef.current] ) const upKey = useDebounceFn( @@ -297,7 +296,6 @@ const Table = (props: TableVirtualResizeProps) => { useHotkeys( "down", () => { - if (!useUpAndDown) return if (!setCurrentRow) return const dataLength = data.length if (dataLength <= 0) { @@ -333,7 +331,7 @@ const Table = (props: TableVirtualResizeProps) => { }, 50) } }, - {enabled: inViewport}, + {enabled: inViewport && useUpAndDown}, [data, currentRow, containerRef.current] ) const downKey = useDebounceFn( diff --git a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.module.scss b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.module.scss index b8b5f23102..125b9f54ae 100644 --- a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.module.scss +++ b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.module.scss @@ -413,7 +413,7 @@ .all-sequence-response-table { height: 100%; overflow: hidden; - border: 1px solid var(--yakit-border-color); + border-top: 1px solid var(--yakit-border-color); border-radius: 4px; } } diff --git a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.tsx b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.tsx index ae5b73db0a..ded82fb72b 100644 --- a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.tsx +++ b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequence.tsx @@ -36,6 +36,7 @@ import { import { OutlineArrowcirclerightIcon, OutlineCodeIcon, + OutlineEyeIcon, OutlinePencilaltIcon, OutlinePlussmIcon, OutlineQuestionmarkcircleIcon, @@ -53,6 +54,7 @@ import { FuzzerExtraShow, FuzzerRequestProps, FuzzerResponse, + FuzzerTableMaxData, ResponseViewer, SecondNodeExtra, SecondNodeTitle, @@ -95,6 +97,7 @@ import sequencemp4 from "@/assets/sequence.mp4" import {prettifyPacketCode} from "@/utils/prettifyPacket" import {Uint8ArrayToString} from "@/utils/str" +const ResponseAllDataCard = React.lazy(() => import("./ResponseAllDataCard")) const ResponseCard = React.lazy(() => import("./ResponseCard")) const {ipcRenderer} = window.require("electron") @@ -166,6 +169,8 @@ const FuzzerSequence: React.FC = React.memo((props) => { const [errorIndex, setErrorIndex] = useState(-1) + const [showAllDataRes, setShowAllDataRes] = useState(false) + const [showAllRes, setShowAllRes] = useState(false) const [showAllResponse, setShowAllResponse] = useState(false) // Request @@ -283,6 +288,8 @@ const FuzzerSequence: React.FC = React.memo((props) => { const failedCountRef = useRef>(new Map()) const successBufferRef = useRef>(new Map()) const failedBufferRef = useRef>(new Map()) + const countRef = useRef>(new Map()) + const runtimeIdBufferRef = useRef>(new Map()) useEffect(() => { if (currentSequenceItem) { @@ -321,14 +328,19 @@ const FuzzerSequence: React.FC = React.memo((props) => { } ) const fuzzerIndexArr = useRef([]) + useEffect(() => { const token = fuzzTokenRef.current const dataToken = `${token}-data` const errToken = `${token}-error` const endToken = `${token}-end` + ipcRenderer.on(dataToken, (e: any, data: FuzzerSequenceResponse) => { const {Response, Request} = data const {FuzzerIndex = ""} = Request + + if (onIsDropped(Response, FuzzerIndex)) return + if (Response.Ok) { // successCount++ let currentSuccessCount = successCountRef.current.get(FuzzerIndex) @@ -349,18 +361,42 @@ const FuzzerSequence: React.FC = React.memo((props) => { } } onSetFirstAsSelected(FuzzerIndex) - if (onIsDropped(Response, FuzzerIndex)) return + + // 用于数据项请求字段 + let count = countRef.current.get(FuzzerIndex) + if (count !== undefined) { + count++ + countRef.current.set(FuzzerIndex, count) + } else { + countRef.current.set(FuzzerIndex, 0) + } + + // 主要用于查看全部数据 + if (Response.RuntimeID) { + let runtimeId = runtimeIdBufferRef.current.get(FuzzerIndex) + if (runtimeId) { + runtimeId.push(Response.RuntimeID) + runtimeIdBufferRef.current.set(FuzzerIndex, [...new Set(runtimeId)]) + } else { + runtimeIdBufferRef.current.set(FuzzerIndex, [Response.RuntimeID]) + } + } + const r = { ...Response, Headers: Response.Headers || [], UUID: Response.UUID, - Count: 0, + Count: countRef.current.get(FuzzerIndex), cellClassName: Response.MatchedByMatcher ? `color-opacity-bg-${Response.HitColor}` : "" } as FuzzerResponse if (Response.Ok) { let successList = successBufferRef.current.get(FuzzerIndex) if (successList) { successList.push(r) + // 超过最大显示 展示最新数据 + if (successList.length > FuzzerTableMaxData) { + successList.shift() + } successBufferRef.current.set(FuzzerIndex, successList) } else { successBufferRef.current.set(FuzzerIndex, [r]) @@ -378,7 +414,6 @@ const FuzzerSequence: React.FC = React.memo((props) => { if (!fuzzerIndexArr.current.includes(FuzzerIndex)) { fuzzerIndexArr.current.push(FuzzerIndex) } - // updateData(FuzzerIndex) }) ipcRenderer.on(endToken, () => { setTimeout(() => { @@ -444,6 +479,7 @@ const FuzzerSequence: React.FC = React.memo((props) => { (fuzzerIndex: string) => { const successBuffer: FuzzerResponse[] = successBufferRef.current.get(fuzzerIndex) || [] const failedBuffer: FuzzerResponse[] = failedBufferRef.current.get(fuzzerIndex) || [] + const runtimeIdBuffer = runtimeIdBufferRef.current.get(fuzzerIndex) || [] if (failedBuffer.length + successBuffer.length === 0) { return } @@ -461,6 +497,7 @@ const FuzzerSequence: React.FC = React.memo((props) => { let currentSuccessCount = successCountRef.current.get(fuzzerIndex) || 0 let currentFailedCount = failedCountRef.current.get(fuzzerIndex) || 0 + if (successBuffer.length + failedBuffer.length === 1) { const onlyOneResponse = successBuffer.length === 1 ? successBuffer[0] : failedBuffer[0] // 设置第一个 response @@ -470,7 +507,8 @@ const FuzzerSequence: React.FC = React.memo((props) => { successCount: currentSuccessCount, failedCount: currentFailedCount, successFuzzer: successBuffer, - failedFuzzer: failedBuffer + failedFuzzer: failedBuffer, + runtimeIdFuzzer: runtimeIdBuffer }) return } @@ -482,7 +520,8 @@ const FuzzerSequence: React.FC = React.memo((props) => { successCount: currentSuccessCount, failedCount: currentFailedCount, successFuzzer: [...successBuffer], - failedFuzzer: [...failedBuffer] + failedFuzzer: [...failedBuffer], + runtimeIdFuzzer: [...runtimeIdBuffer] } setResponse(fuzzerIndex, newResponse) }, @@ -494,6 +533,8 @@ const FuzzerSequence: React.FC = React.memo((props) => { failedCountRef.current.clear() successBufferRef.current.clear() failedBufferRef.current.clear() + countRef.current.clear() + runtimeIdBufferRef.current.clear() }) /** * 组内的webFuzzer页面高级配置或者request发生变化,或者组发生变化, @@ -767,9 +808,6 @@ const FuzzerSequence: React.FC = React.memo((props) => { } setCurrentSequenceItem({...val}) }) - const onSetShowAllResponse = useMemoizedFn(() => { - setShowAllResponse(false) - }) const setHotPatchCode = useMemoizedFn((val) => { hotPatchCodeRef.current = val @@ -777,11 +815,41 @@ const FuzzerSequence: React.FC = React.memo((props) => { const setHotPatchCodeWithParamGetter = useMemoizedFn((val) => { hotPatchCodeWithParamGetterRef.current = val }) + + const allRuntimeIds = () => { + const myArray1 = Array.from(responseMap) + const length = myArray1.length + const runtimeIdsFuzzer: string[] = [] + for (let index = 0; index < length; index++) { + const element = myArray1[index][1] + if (element) { + runtimeIdsFuzzer.push(...element.runtimeIdFuzzer) + } + } + return [...new Set(runtimeIdsFuzzer)] + } + + const judgeMoreFuzzerTableMaxData = () => { + const myArray1 = Array.from(responseMap) + const length = myArray1.length + let flag: boolean = false + for (let index = 0; index < length; index++) { + const element = myArray1[index][1] + if (element) { + if (element.successCount >= FuzzerTableMaxData) { + flag = true + return flag + } + } + } + return flag + } + return ( <>
@@ -934,7 +1002,12 @@ const FuzzerSequence: React.FC = React.memo((props) => { advancedConfigValue={currentSelectRequest?.advancedConfigValue} droppedCount={getDroppedCount(currentSequenceItem.id) || 0} onShowAll={() => { - setShowAllResponse(true) + if (judgeMoreFuzzerTableMaxData()) { + setShowAllRes(true) + setShowAllDataRes(true) + } else { + setShowAllResponse(true) + } }} getHttpParams={getHttpParams} /> @@ -948,6 +1021,9 @@ const FuzzerSequence: React.FC = React.memo((props) => { hotPatchCodeWithParamGetter={hotPatchCodeWithParamGetterRef.current} setHotPatchCode={setHotPatchCode} setHotPatchCodeWithParamGetter={setHotPatchCodeWithParamGetter} + onShowAll={() => { + setShowAllDataRes(true) + }} /> ) : ( @@ -955,12 +1031,26 @@ const FuzzerSequence: React.FC = React.memo((props) => { )}
- loading allFuzzerSequenceList ...}> + + loading...}> + { + setShowAllRes(false) + setShowAllDataRes(false) + }} + /> + + + loading...}> + setShowAllResponse={() => setShowAllResponse(false)} + > ) @@ -1356,7 +1446,8 @@ const SequenceResponse: React.FC = React.memo( hotPatchCode, setHotPatchCode, hotPatchCodeWithParamGetter, - setHotPatchCodeWithParamGetter + setHotPatchCodeWithParamGetter, + onShowAll } = props const { id: responseInfoId, @@ -1573,14 +1664,44 @@ const SequenceResponse: React.FC = React.memo( cachedTotal={cachedTotal} onlyOneResponse={onlyOneResponse} rsp={httpResponse} - successFuzzerLength={(successFuzzer || []).length} - failedFuzzerLength={(failedFuzzer || []).length} + successFuzzerLength={successCount} + failedFuzzerLength={failedCount} showSuccess={showSuccess} setShowSuccess={(v) => { setShowSuccess(v) setQuery({}) }} /> + {successFuzzer.length >= FuzzerTableMaxData && ( + <> + {+(secondNodeSize?.width || 0) <= 750 ? ( + + } + onClick={() => { + onShowAll() + }} + disabled={loading} + /> + + ) : ( + { + onShowAll() + }} + disabled={loading} + > + 查看全部 + + )} + + )} ) @@ -1765,6 +1886,7 @@ const SequenceResponse: React.FC = React.memo( extractedMap={extractedMap} isEnd={loading} onDebug={onDebug} + moreLimtAlertMsg='响应数量超过2w,为避免前端渲染压力过大,这里将丢弃部分数据包进行展示,请点击”查看全部“查看所有数据' /> )} {!showSuccess && ( diff --git a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequenceType.d.ts b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequenceType.d.ts index fa3706e571..e80d2250f2 100644 --- a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequenceType.d.ts +++ b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/FuzzerSequenceType.d.ts @@ -87,6 +87,7 @@ export interface ResponseProps { onlyOneResponse: FuzzerResponse successFuzzer: FuzzerResponse[] failedFuzzer: FuzzerResponse[] + runtimeIdFuzzer: string[] } /** * @description HTTPFuzzerSequence接口返回类型 @@ -119,6 +120,7 @@ export interface SequenceResponseProps { hotPatchCodeWithParamGetter: string setHotPatchCode: (s: string) => void setHotPatchCodeWithParamGetter: (s: string) => void + onShowAll: () => void } export interface SequenceResponseRefProps{ @@ -156,3 +158,12 @@ export interface ResponseCardProps { responseMap: Map setShowAllResponse: () => void } + +/** + * @description 展示所有响应的card + */ +export interface ResponseAllDataCardProps { + showAllDataRes: boolean + setShowAllDataRes: () => void + runtimeId: string +} diff --git a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/ResponseAllDataCard.tsx b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/ResponseAllDataCard.tsx new file mode 100644 index 0000000000..d4d488c31f --- /dev/null +++ b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/ResponseAllDataCard.tsx @@ -0,0 +1,49 @@ +import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" +import React, {useEffect, useState} from "react" +import {ResponseAllDataCardProps} from "./FuzzerSequenceType" +import styles from "./FuzzerSequence.module.scss" +import {OutlineReplyIcon} from "@/assets/icon/outline" +import {CurrentHttpFlow} from "@/pages/yakitStore/viewers/base" + +const ResponseAllDataCard: React.FC = React.memo((props) => { + const {showAllDataRes, setShowAllDataRes, runtimeId} = props + const [onlyShowFirstNode, setOnlyShowFirstNode] = useState(true) + const [refresh, setRefresh] = useState(false) + + useEffect(() => { + setOnlyShowFirstNode(true) + setRefresh(!refresh) + }, [showAllDataRes]) + + return showAllDataRes ? ( +
+
+
+ { + setShowAllDataRes() + }} + type='text2' + icon={} + > + 返回 + +
+
+
+ +
+
+ ) : ( + <> + ) +}) + +export default ResponseAllDataCard diff --git a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/ResponseCard.tsx b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/ResponseCard.tsx index 6c5bf61a49..169232dfa1 100644 --- a/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/ResponseCard.tsx +++ b/app/renderer/src/main/src/pages/fuzzer/FuzzerSequence/ResponseCard.tsx @@ -15,6 +15,7 @@ import styles from "./FuzzerSequence.module.scss" import {Divider, Result} from "antd" import {HTTPFuzzerPageTable, HTTPFuzzerPageTableQuery} from "../components/HTTPFuzzerPageTable/HTTPFuzzerPageTable" import {OutlineReplyIcon} from "@/assets/icon/outline" +import { CurrentHttpFlow } from "@/pages/yakitStore/viewers/base" const {ipcRenderer} = window.require("electron") @@ -37,6 +38,8 @@ const ResponseCard: React.FC = React.memo((props) => { const secondNodeRef = useRef(null) const secondNodeSize = useSize(secondNodeRef) + const [onlyShowFirstNode, setOnlyShowFirstNode] = useState(true) + useEffect(() => { ipcRenderer.on( "fetch-extracted-to-table", @@ -126,12 +129,15 @@ const ResponseCard: React.FC = React.memo((props) => { setShowResponseInfoSecondEditor={()=>{}} /> - setShowAllResponse()} type='text2' icon={}> + { + setOnlyShowFirstNode(true) + setShowAllResponse() + }} type='text2' icon={}> 返回 -
+
{showSuccess && ( import("./FuzzerSequence/ResponseAllDataCard")) const {ipcRenderer} = window.require("electron") @@ -215,6 +217,8 @@ export interface FuzzerResponse { TooLargeResponseHeaderFile: string TooLargeResponseBodyFile: string DisableRenderStyles: boolean + + RuntimeID: string } export const defaultPostTemplate = `POST / HTTP/1.1 @@ -533,7 +537,8 @@ export const emptyFuzzer: FuzzerResponse = { IsTooLargeResponse: false, TooLargeResponseHeaderFile: "", TooLargeResponseBodyFile: "", - DisableRenderStyles: false + DisableRenderStyles: false, + RuntimeID: "" } export interface SelectOptionProps { @@ -623,6 +628,9 @@ export const defaultAdvancedConfigValue: AdvancedConfigValueProps = { extractors: [] } +// WebFuzzer表格最多显示多少数据 +export const FuzzerTableMaxData = 20000 + const HTTPFuzzerPage: React.FC = (props) => { const {queryPagesDataById, updatePagesDataCacheById} = usePageInfo( (s) => ({ @@ -862,6 +870,8 @@ const HTTPFuzzerPage: React.FC = (props) => { setFailedFuzzer([]) setSuccessCount(0) setFailedCount(0) + taskIDRef.current = "" + setRuntimeId("") }) // 从历史记录中恢复 @@ -1074,7 +1084,7 @@ const HTTPFuzzerPage: React.FC = (props) => { const resumeAndPause = useMemoizedFn(async () => { try { if (!taskIDRef.current) return - await ipcRenderer.invoke("HTTPFuzzer", {PauseTaskID: taskIDRef.current, IsPause: isPause}, tokenRef.current) + await ipcRenderer.invoke("HTTPFuzzer", {PauseTaskID: taskIDRef.current, IsPause: isPause, SetPauseStatus:true}, tokenRef.current) setLoading(!isPause) setIsPause(!isPause) } catch (error) { @@ -1087,7 +1097,9 @@ const HTTPFuzzerPage: React.FC = (props) => { }) const dCountRef = useRef(0) const tokenRef = useRef(randomString(60)) - const taskIDRef = useRef(0) + const taskIDRef = useRef("") + const [showAllDataRes, setShowAllDataRes] = useState(false) + const [runtimeId, setRuntimeId] = useState("") useEffect(() => { const token = tokenRef.current @@ -1108,72 +1120,71 @@ const HTTPFuzzerPage: React.FC = (props) => { let successBuffer: FuzzerResponse[] = [] let failedBuffer: FuzzerResponse[] = [] - let count: number = 0 - let lastUpdateCount: number = 0 + let count: number = 0 // 用于数据项请求字段 + const updateData = () => { if (count <= 0) { return } - if (failedBuffer.length + successBuffer.length === 0) { + if (failedBuffer.length + successBuffer.length + failedCount + successCount === 0) { return } - if (lastUpdateCount <= 0 || lastUpdateCount != count || count === 1) { - // setContent([...buffer]) - setSuccessFuzzer([...successBuffer]) - setFailedFuzzer([...failedBuffer]) - setFailedCount(failedCount) - setSuccessCount(successCount) - lastUpdateCount = count - } + setSuccessFuzzer([...successBuffer]) + setFailedFuzzer([...failedBuffer]) + setFailedCount(failedCount) + setSuccessCount(successCount) } ipcRenderer.on(dataToken, (e: any, data: any) => { taskIDRef.current = data.TaskId + setRuntimeId(data.RuntimeID) + if (count === 0) { // 重置extractedMap reset() } - if (data.Ok) { - successCount++ - } else { - failedCount++ - } + if (onIsDropped(data)) return + const r = { // 6.16 ...data, Headers: data.Headers || [], UUID: data.UUID || randomString(16), // 新版yakit,成功和失败的数据都有UUID,旧版失败的数据没有UUID,兼容 - Count: count, + Count: count++, cellClassName: data.MatchedByMatcher ? `color-opacity-bg-${data.HitColor} color-text-${data.HitColor} color-font-weight-${data.HitColor}` : "" } as FuzzerResponse + // 设置第一个 response if (getFirstResponse().RequestRaw.length === 0) { setFirstResponse(r) } + if (data.Ok) { - // if (r.MatchedByMatcher) { - // yakitNotify("success", `匹配成功: ${r.Url}`) - // } + successCount++ successBuffer.push(r) + // 超过最大显示 展示最新数据 + if (successBuffer.length > FuzzerTableMaxData) { + successBuffer.shift() + } } else { + failedCount++ failedBuffer.push(r) } - count++ - // setContent([...buffer]) }) + ipcRenderer.on(endToken, () => { updateData() successBuffer = [] failedBuffer = [] count = 0 + successCount = 0 + failedCount = 0 dCountRef.current = 0 - lastUpdateCount = 0 - taskIDRef.current = 0 setTimeout(() => { setIsPause(true) setLoading(false) @@ -1183,17 +1194,17 @@ const HTTPFuzzerPage: React.FC = (props) => { const updateDataId = setInterval(() => { updateData() - }, 200) + }, 300) return () => { ipcRenderer.invoke("cancel-HTTPFuzzer", token) - clearInterval(updateDataId) ipcRenderer.removeAllListeners(errToken) ipcRenderer.removeAllListeners(dataToken) ipcRenderer.removeAllListeners(endToken) } }, []) + const [extractedMap, {setAll, reset}] = useMap() useEffect(() => { ipcRenderer.on( @@ -1651,14 +1662,44 @@ const HTTPFuzzerPage: React.FC = (props) => { cachedTotal={cachedTotal} onlyOneResponse={onlyOneResponse} rsp={httpResponse} - successFuzzerLength={(successFuzzer || []).length} - failedFuzzerLength={(failedFuzzer || []).length} + successFuzzerLength={getSuccessCount()} + failedFuzzerLength={getFailedCount()} showSuccess={showSuccess} setShowSuccess={(v) => { setShowSuccess(v) setQuery(undefined) }} /> + {successFuzzer.length >= FuzzerTableMaxData && ( + <> + {+(secondNodeSize?.width || 0) <= 750 ? ( + + } + onClick={() => { + setShowAllDataRes(true) + }} + disabled={loading} + /> + + ) : ( + { + setShowAllDataRes(true) + }} + disabled={loading} + > + 查看全部 + + )} + + )} ) @@ -1758,373 +1799,386 @@ const HTTPFuzzerPage: React.FC = (props) => { } return ( -
- 加载中...}> - - -
-
-
- {!loading ? ( - <> - {!isPause ? ( + <> +
+ 加载中...}> + + +
+
+
+ {!loading ? ( + <> + {!isPause ? ( + } + type={"primary"} + size='large' + > + 继续 + + ) : ( + { + setRedirectedResponse(undefined) + sendFuzzerSettingInfo() + onValidateHTTPFuzzer() + getNewCurrentPage() + }} + icon={} + type={"primary"} + size='large' + > + 发送请求 + + )} + + ) : ( + <> } + icon={} type={"primary"} size='large' > - 继续 + 暂停 - ) : ( { - setRedirectedResponse(undefined) - sendFuzzerSettingInfo() - onValidateHTTPFuzzer() - getNewCurrentPage() + cancelCurrentHTTPFuzzer() }} - icon={} + icon={} type={"primary"} + colors='danger' size='large' + style={{marginLeft: -8}} > - 发送请求 + 停止 - )} - - ) : ( - <> - } - type={"primary"} - size='large' + + )} +
+ 强制 HTTPS + + setAdvancedConfigValue({...advancedConfigValue, isHttps: e.target.checked}) + } + /> +
+ {/*
*/} + {/* 国密TLS*/} + {/* */} + {/* setAdvancedConfigValue({...advancedConfigValue, isGmTLS: e.target.checked})*/} + {/* }*/} + {/* />*/} + {/*
*/} + +
+ + { + cancelCurrentHTTPFuzzer() + if (!showAll) setCurrentPage(page) + loadHistory(e) + }} + onDeleteAllCallback={() => { + setCurrentPage(0) + getTotal() + }} + fuzzerTabIndex={props.id} + /> +
+ } > - 暂停 -
+ } style={{padding: "4px 0px"}}> + 历史 + + +
+
{ + const m = showYakitModal({ + type: "white", + title: "WebFuzzer 爆破动画演示", + width: 480, + content: , + footer: null, + centered: true, + destroyOnClose: true + }) + }} + > + 爆破示例 + +
+ {loading && ( +
+ + sending packets +
+ )} + + {onlyOneResponse && httpResponse.Ok && checkRedirect && ( { - cancelCurrentHTTPFuzzer() + setLoading(true) + const redirectRequestProps: RedirectRequestParams = { + Request: requestRef.current, + Response: new Buffer(httpResponse.ResponseRaw).toString("utf8"), + IsHttps: advancedConfigValue.isHttps, + IsGmTLS: advancedConfigValue.isGmTLS, + PerRequestTimeoutSeconds: advancedConfigValue.timeout, + Proxy: advancedConfigValue.proxy.join(","), + Extractors: advancedConfigValue.extractors, + Matchers: advancedConfigValue.matchers, + MatchersCondition: advancedConfigValue.matchersCondition, + HitColor: + advancedConfigValue.filterMode === "onlyMatch" + ? advancedConfigValue.hitColor + : "", + Params: advancedConfigValue.params || [] + } + ipcRenderer + .invoke("RedirectRequest", redirectRequestProps) + .then((rsp: FuzzerResponse) => { + setRedirectedResponse(rsp) + }) + .catch((e) => { + failed(`"ERROR in: ${e}"`) + }) + .finally(() => { + setTimeout(() => setLoading(false), 300) + }) }} - icon={} - type={"primary"} - colors='danger' - size='large' - style={{marginLeft: -8}} + type='outline2' > - 停止 + 跟随重定向 - - )} -
- 强制 HTTPS - - setAdvancedConfigValue({...advancedConfigValue, isHttps: e.target.checked}) - } + )} +
- {/*
*/} - {/* 国密TLS*/} - {/* */} - {/* setAdvancedConfigValue({...advancedConfigValue, isGmTLS: e.target.checked})*/} - {/* }*/} - {/* />*/} - {/*
*/} - -
- - { - cancelCurrentHTTPFuzzer() - if (!showAll) setCurrentPage(page) - loadHistory(e) - }} - onDeleteAllCallback={() => { - setCurrentPage(0) - getTotal() - }} - fuzzerTabIndex={props.id} - /> -
- } - > - } style={{padding: "4px 0px"}}> - 历史 - - -
-
{ - const m = showYakitModal({ - type: "white", - title: "WebFuzzer 爆破动画演示", - width: 480, - content: , - footer: null, - centered: true, - destroyOnClose: true - }) - }} - > - 爆破示例 - -
- {loading && ( -
- - sending packets -
- )} - - {onlyOneResponse && httpResponse.Ok && checkRedirect && ( - { - setLoading(true) - const redirectRequestProps: RedirectRequestParams = { - Request: requestRef.current, - Response: new Buffer(httpResponse.ResponseRaw).toString("utf8"), - IsHttps: advancedConfigValue.isHttps, - IsGmTLS: advancedConfigValue.isGmTLS, - PerRequestTimeoutSeconds: advancedConfigValue.timeout, - Proxy: advancedConfigValue.proxy.join(","), - Extractors: advancedConfigValue.extractors, - Matchers: advancedConfigValue.matchers, - MatchersCondition: advancedConfigValue.matchersCondition, - HitColor: - advancedConfigValue.filterMode === "onlyMatch" - ? advancedConfigValue.hitColor - : "", - Params: advancedConfigValue.params || [] +
+ + + { + switch (key) { + case "pathTemplate": + handleSkipPluginDebuggerPage("path") + break + case "rawTemplate": + handleSkipPluginDebuggerPage("raw") + break + default: + break + } } - ipcRenderer - .invoke("RedirectRequest", redirectRequestProps) - .then((rsp: FuzzerResponse) => { - setRedirectedResponse(rsp) - }) - .catch((e) => { - failed(`"ERROR in: ${e}"`) - }) - .finally(() => { - setTimeout(() => setLoading(false), 300) - }) }} - type='outline2' + dropdown={{ + trigger: ["click"], + placement: "bottom" + }} > - 跟随重定向 - - )} - -
-
- - - { - switch (key) { - case "pathTemplate": - handleSkipPluginDebuggerPage("path") - break - case "rawTemplate": - handleSkipPluginDebuggerPage("raw") - break - default: - break - } - } - }} - dropdown={{ - trigger: ["click"], - placement: "bottom" - }} - > - }> - 生成 Yaml 模板 - - + }> + 生成 Yaml 模板 + + +
-
- - } - secondNode={ -
- {onlyOneResponse ? ( - { - setAdvancedConfigValue({ - ...advancedConfigValue, - filterMode: matcher.filterMode, - hitColor: matcher.hitColor || "red", - matchersCondition: matcher.matchersCondition, - matchers: matcher.matchersList, - extractors: extractor.extractorList - }) - }} - webFuzzerValue={StringToUint8Array(requestRef.current)} - showResponseInfoSecondEditor={showResponseInfoSecondEditor} - setShowResponseInfoSecondEditor={setShowResponseInfoSecondEditor} - secondNodeTitle={secondNodeTitle} - secondNodeExtra={secondNodeExtra} - /> - ) : ( - <> -
-
-
{secondNodeTitle()}
-
- {secondNodeExtra()} + + } + secondNode={ +
+ {onlyOneResponse ? ( + { + setAdvancedConfigValue({ + ...advancedConfigValue, + filterMode: matcher.filterMode, + hitColor: matcher.hitColor || "red", + matchersCondition: matcher.matchersCondition, + matchers: matcher.matchersList, + extractors: extractor.extractorList + }) + }} + webFuzzerValue={StringToUint8Array(requestRef.current)} + showResponseInfoSecondEditor={showResponseInfoSecondEditor} + setShowResponseInfoSecondEditor={setShowResponseInfoSecondEditor} + secondNodeTitle={secondNodeTitle} + secondNodeExtra={secondNodeExtra} + /> + ) : ( + <> +
+
+
+ {secondNodeTitle()} +
+
+ {secondNodeExtra()} +
+ {cachedTotal >= 1 ? ( + <> + {showSuccess && ( + + )} + {!showSuccess && ( + + )} + + ) : ( + + )}
- {cachedTotal >= 1 ? ( - <> - {showSuccess && ( - - )} - {!showSuccess && ( - - )} - - ) : ( - - )} -
- - )} -
- } - /> + + )} +
+ } + /> +
+ {fuzzerRef.current && ( + { + setYakitWindowVisible(false) + setMenuExecutorParams(undefined) + }} + onOk={() => {}} + > + {menuExecutorParams && ( + + )} + + )}
- {fuzzerRef.current && ( - { - setYakitWindowVisible(false) - setMenuExecutorParams(undefined) - }} - onOk={() => {}} - > - {menuExecutorParams && ( - - )} - - )} -
+ loading...}> + setShowAllDataRes(false)} + /> + + ) } export default HTTPFuzzerPage @@ -2452,8 +2506,8 @@ export const SecondNodeExtra: React.FC = React.memo((props return (
- {+(secondNodeSize?.width || 0) >= 680 && searchNode} - {+(secondNodeSize?.width || 0) < 680 && ( + {+(secondNodeSize?.width || 0) >= 700 && searchNode} + {+(secondNodeSize?.width || 0) < 700 && ( { diff --git a/app/renderer/src/main/src/pages/fuzzer/HttpQueryAdvancedConfig/HttpQueryAdvancedConfig.tsx b/app/renderer/src/main/src/pages/fuzzer/HttpQueryAdvancedConfig/HttpQueryAdvancedConfig.tsx index a91e8d791c..a7c3c81869 100644 --- a/app/renderer/src/main/src/pages/fuzzer/HttpQueryAdvancedConfig/HttpQueryAdvancedConfig.tsx +++ b/app/renderer/src/main/src/pages/fuzzer/HttpQueryAdvancedConfig/HttpQueryAdvancedConfig.tsx @@ -870,6 +870,7 @@ export const HttpQueryAdvancedConfig: React.FC = R ? `${i.Key} => ${i.Value}` : "" } + key={`${i.Key} => ${i.Value}`} > = React.memo( } = props const [reqEditor, setReqEditor] = useState() + const [newRequest, setNewRequest] = useState(request) // 由于传过来的request是ref 值变化并不会导致重渲染 这里拿到的request还是旧值 + useImperativeHandle( ref, () => ({ @@ -142,7 +144,7 @@ export const WebFuzzerNewEditor: React.FC = React.memo( onRun: (editor, key) => { switch (key) { case "copy-as-url": - copyAsUrl({Request: request, IsHTTPS: isHttps}) + copyAsUrl({Request: newRequest, IsHTTPS: isHttps}) return case "copy-as-curl": execCodec("packet-to-curl", request, undefined, undefined, undefined, [ @@ -158,7 +160,7 @@ export const WebFuzzerNewEditor: React.FC = React.memo( } } } - }, [request, isHttps]) + }, [newRequest, isHttps]) return ( = React.memo( originValue={StringToUint8Array(request)} contextMenu={editorRightMenu} onEditor={setReqEditor} - onChange={(i) => setRequest(Uint8ArrayToString(i, "utf8"))} + onChange={(i) => { + setNewRequest(Uint8ArrayToString(i, "utf8")) + setRequest(Uint8ArrayToString(i, "utf8")) + }} editorOperationRecord='HTTP_FUZZER_PAGE_EDITOR_RECORF' extraEditorProps={{ isShowSelectRangeMenu: true diff --git a/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.module.scss b/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.module.scss index 8c866bb7cc..5b5e89785b 100644 --- a/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.module.scss +++ b/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.module.scss @@ -33,15 +33,18 @@ color: var(--yakit-danger-5); } -.http-fuzzer-page-table{ +.http-fuzzer-page-table { :global { - .ant-card-bordered{ + .ant-card-bordered { border-left: transparent; border-right: transparent; border-top: transparent; - } + } + } + + .fuzzer-page-table-wrap { + height: 100%; } - } .unit-select { diff --git a/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.tsx b/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.tsx index 68769a70a8..de691f5118 100644 --- a/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.tsx +++ b/app/renderer/src/main/src/pages/fuzzer/components/HTTPFuzzerPageTable/HTTPFuzzerPageTable.tsx @@ -9,7 +9,12 @@ import {OtherMenuListProps} from "@/components/yakitUI/YakitEditor/YakitEditorTy import {YakitSelect} from "@/components/yakitUI/YakitSelect/YakitSelect" import {CopyComponents, YakitTag} from "@/components/yakitUI/YakitTag/YakitTag" import {compareAsc, compareDesc} from "@/pages/yakitStore/viewers/base" -import {HTTP_PACKET_EDITOR_Response_Info, IMonacoEditor, NewHTTPPacketEditor, RenderTypeOptionVal} from "@/utils/editors" +import { + HTTP_PACKET_EDITOR_Response_Info, + IMonacoEditor, + NewHTTPPacketEditor, + RenderTypeOptionVal +} from "@/utils/editors" import {getRemoteValue, setRemoteValue} from "@/utils/kv" import {failed, yakitFailed, yakitNotify} from "@/utils/notification" import {Uint8ArrayToString} from "@/utils/str" @@ -18,11 +23,11 @@ import {useCreation, useDebounceFn, useMemoizedFn, useThrottleEffect, useUpdateE import classNames from "classnames" import moment from "moment" import React, {useEffect, useImperativeHandle, useMemo, useRef, useState} from "react" -import {analyzeFuzzerResponse, FuzzerResponse, onAddOverlayWidget} from "../../HTTPFuzzerPage" +import {analyzeFuzzerResponse, FuzzerResponse, FuzzerTableMaxData, onAddOverlayWidget} from "../../HTTPFuzzerPage" import styles from "./HTTPFuzzerPageTable.module.scss" import {ArrowRightSvgIcon} from "@/components/layout/icons" import {HollowLightningBoltIcon} from "@/assets/newIcon" -import {Divider, Space, Tooltip} from "antd" +import {Alert, Divider, Space, Tooltip} from "antd" import {ExtractionResultsContent} from "../../MatcherAndExtractionCard/MatcherAndExtractionCard" import {showYakitModal} from "@/components/yakitUI/YakitModal/YakitModalConfirm" import {YakitCard} from "@/components/yakitUI/YakitCard/YakitCard" @@ -31,7 +36,9 @@ import {YakitResizeBox} from "@/components/yakitUI/YakitResizeBox/YakitResizeBox import emiter from "@/utils/eventBus/eventBus" import {YakitDropdownMenu} from "@/components/yakitUI/YakitDropdownMenu/YakitDropdownMenu" import {openABSFileLocated} from "@/utils/openWebsite" -import { RemoteGV } from "@/yakitGV" +import {RemoteGV} from "@/yakitGV" +import {OutlineXIcon} from "@/assets/icon/outline" +import ReactResizeDetector from "react-resize-detector" const {ipcRenderer} = window.require("electron") @@ -53,6 +60,9 @@ interface HTTPFuzzerPageTableProps { /**点击调试回调 */ onDebug?: (res: string) => void pageId?: string + /**超过限制数据,alert文案显示 */ + moreLimtAlertMsg?: string + tableKeyUpDownEnabled?: boolean } /** @@ -131,7 +141,9 @@ export const HTTPFuzzerPageTable: React.FC = React.mem setExportData, isShowDebug, onDebug, - pageId + pageId, + moreLimtAlertMsg = "", + tableKeyUpDownEnabled = true } = props const [listTable, setListTable] = useState([]) const listTableRef = useRef([]) @@ -152,6 +164,8 @@ export const HTTPFuzzerPageTable: React.FC = React.mem const tableRef = useRef(null) const [scrollToIndex, setScrollToIndex] = useState() + const [alertClose, setAlertClose] = useState(false) + const [alertHeight, setAlertHeight] = useState(0) useEffect(() => { sorterTableRef.current = sorterTable @@ -203,7 +217,7 @@ export const HTTPFuzzerPageTable: React.FC = React.mem title: "请求", dataKey: "Count", render: (v) => { - return v + 1 + return v + 1 }, width: 80, sorterProps: { @@ -323,7 +337,9 @@ export const HTTPFuzzerPageTable: React.FC = React.mem sorterProps: { sorter: true }, - render: (v) => (v ? v.join(",") : "-") + render: (v) => { + return v ? v.join(",") : "-" + } }, { title: "提取数据", @@ -488,7 +504,9 @@ export const HTTPFuzzerPageTable: React.FC = React.mem sorterProps: { sorter: true }, - render: (v) => v.join(",") + render: (v) => { + return v ? v.join(",") : "-" + } } ] }, [success, query?.afterBodyLength, query?.beforeBodyLength, extractedMap, isHaveData, isShowDebug]) @@ -566,16 +584,13 @@ export const HTTPFuzzerPageTable: React.FC = React.mem wait: 200 } ).run + /** * @description 前端搜索 */ const queryData = useMemoizedFn(() => { try { // ------------ 搜索 开始 ------------ - const copyData = structuredClone(data) - copyData.forEach((item, index) => { - item.Count = index - }) // 有搜索条件才循环 if ( query?.keyWord || @@ -584,7 +599,7 @@ export const HTTPFuzzerPageTable: React.FC = React.mem query?.beforeBodyLength || isHaveData ) { - const newDataTable = sorterFunction(copyData, sorterTable) || [] + const newDataTable = sorterFunction(data, sorterTable) || [] const l = newDataTable.length const searchList: FuzzerResponse[] = [] for (let index = 0; index < l; index++) { @@ -651,7 +666,7 @@ export const HTTPFuzzerPageTable: React.FC = React.mem scrollUpdate(searchList.length) } } else { - const newData = sorterFunction(copyData, sorterTable) || [] + const newData = sorterFunction(data, sorterTable) || [] setExportData && setExportData([...newData]) setListTable([...newData]) if (newData.length > 0) { @@ -748,7 +763,7 @@ export const HTTPFuzzerPageTable: React.FC = React.mem const [typeOptionVal, setTypeOptionVal] = useState() useEffect(() => { if (currentSelectItem) { - getRemoteValue(RemoteGV.WebFuzzerEditorBeautify).then(res => { + getRemoteValue(RemoteGV.WebFuzzerEditorBeautify).then((res) => { if (!!res) { setTypeOptionVal(res) } else { @@ -766,26 +781,69 @@ export const HTTPFuzzerPageTable: React.FC = React.mem lineStyle={{display: firstFull ? "none" : ""}} secondNodeStyle={{padding: firstFull ? 0 : undefined, display: firstFull ? "none" : ""}} firstNode={ - - ref={tableRef} - query={query} - isRefresh={isRefresh || loading} - titleHeight={0.01} - renderTitle={<>} - renderKey='UUID' - data={listTable} - loading={loading} - enableDrag={true} - columns={columns} - onChange={onTableChange} - containerClassName={classNames(styles["table-container"], { - [styles["table-container-border"]]: currentSelectItem?.ResponseRaw - })} - currentSelectItem={currentSelectItem} - onSetCurrentRow={onSetCurrentRow} - useUpAndDown={true} - scrollToIndex={scrollToIndex} - /> +
+ {moreLimtAlertMsg && data.length >= FuzzerTableMaxData && ( +
+ { + if (!w || !h) { + return + } + setAlertHeight(h) + }} + handleHeight={true} + refreshMode={"debounce"} + refreshRate={50} + /> + } + /> + } + style={{margin: "5px 0"}} + onClose={(e) => { + setAlertClose(true) + }} + /> +
+ )} +
= FuzzerTableMaxData && !alertClose + ? `calc(100% - ${alertHeight + 10}px)` + : "100%" + }} + > + + ref={tableRef} + query={query} + isRefresh={isRefresh || loading} + titleHeight={0.01} + renderTitle={<>} + renderKey='UUID' + data={listTable} + loading={loading} + enableDrag={true} + columns={columns} + onChange={onTableChange} + containerClassName={classNames(styles["table-container"], { + [styles["table-container-border"]]: currentSelectItem?.ResponseRaw + })} + currentSelectItem={currentSelectItem} + onSetCurrentRow={onSetCurrentRow} + useUpAndDown={tableKeyUpDownEnabled} + scrollToIndex={scrollToIndex} + /> +
+
} secondNode={ void + refresh?: boolean // 是否刷新表格 + + toWebFuzzer?: boolean // 是否是在webFuzzer使用 + showBatchActions?: boolean } export const CurrentHttpFlow: React.FC = (props) => { @@ -466,7 +470,10 @@ export const CurrentHttpFlow: React.FC = (props) => { onIsOnlyTable, showDetail, pageType, - onQueryParams + onQueryParams, + refresh = true, + toWebFuzzer = false, + showBatchActions = false } = props const [highlightSearch, setHighlightSearch] = useState("") const lasetIdRef = useRef() @@ -570,11 +577,14 @@ export const CurrentHttpFlow: React.FC = (props) => { ...(httpHistoryTableTitleStyle||{}), }} onlyShowSearch={true} + showBatchActions={showBatchActions} historyId={historyId} titleHeight={47} containerClassName={containerClassName} pageType={pageType} onQueryParams={onQueryParams} + refresh={refresh} + toWebFuzzer={toWebFuzzer} /> } secondNode={ diff --git a/app/renderer/src/main/src/utils/yakQueryHTTPFlow.tsx b/app/renderer/src/main/src/utils/yakQueryHTTPFlow.tsx index 7f388afe0a..68f5f6ba9a 100644 --- a/app/renderer/src/main/src/utils/yakQueryHTTPFlow.tsx +++ b/app/renderer/src/main/src/utils/yakQueryHTTPFlow.tsx @@ -32,6 +32,8 @@ export interface YakQueryHTTPFlowRequest { IsWebsocket?: string FromPlugin?: string RuntimeId?: string + WithPayload?: boolean + RuntimeIDs?: string[] } export interface Paging {