fix(routes): strictly parse provider edit indexes

This commit is contained in:
LTbinglingfeng
2026-05-20 01:29:55 +08:00
parent 2cb98ad118
commit eab19952e0
6 changed files with 20 additions and 35 deletions
+2 -7
View File
@@ -13,6 +13,7 @@ import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } f
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
import { modelsToEntries } from '@/components/ui/modelInputListUtils';
import type { ClaudeEditBaseline } from '@/stores/useClaudeEditDraftStore';
import { parseRouteIndexParam } from '@/utils/routeParams';
type LocationState = { fromAiProviders?: boolean } | null;
@@ -53,12 +54,6 @@ const buildEmptyForm = (): ProviderFormState => ({
excludedText: '',
});
const parseIndexParam = (value: string | undefined) => {
if (!value) return null;
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) ? parsed : null;
};
const getErrorMessage = (err: unknown) => {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
@@ -122,7 +117,7 @@ export function AiProvidersClaudeEditLayout() {
const params = useParams<{ index?: string }>();
const hasIndexParam = typeof params.index === 'string';
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
const invalidIndexParam = hasIndexParam && editIndex === null;
const connectionStatus = useAuthStore((state) => state.connectionStatus);
+2 -7
View File
@@ -17,6 +17,7 @@ import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
import type { ProviderKeyConfig } from '@/types';
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } from '@/utils/compare';
import { parseRouteIndexParam } from '@/utils/routeParams';
import { entriesToModels, modelsToEntries } from '@/components/ui/modelInputListUtils';
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
import type { ProviderFormState } from '@/components/providers';
@@ -40,12 +41,6 @@ const buildEmptyForm = (): ProviderFormState => ({
excludedText: '',
});
const parseIndexParam = (value: string | undefined) => {
if (!value) return null;
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) ? parsed : null;
};
const getErrorMessage = (err: unknown) => {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
@@ -121,7 +116,7 @@ export function AiProvidersCodexEditPage() {
const modelDiscoveryRequestIdRef = useRef(0);
const hasIndexParam = typeof params.index === 'string';
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
const invalidIndexParam = hasIndexParam && editIndex === null;
const initialData = useMemo(() => {
+2 -7
View File
@@ -16,6 +16,7 @@ import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
import type { GeminiKeyConfig } from '@/types';
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } from '@/utils/compare';
import { parseRouteIndexParam } from '@/utils/routeParams';
import type { ModelInfo } from '@/utils/models';
import { entriesToModels, modelsToEntries } from '@/components/ui/modelInputListUtils';
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
@@ -37,12 +38,6 @@ const buildEmptyForm = (): GeminiFormState => ({
excludedText: '',
});
const parseIndexParam = (value: string | undefined) => {
if (!value) return null;
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) ? parsed : null;
};
const stripGeminiModelResourceName = (value: string) => {
return String(value ?? '')
.trim()
@@ -116,7 +111,7 @@ export function AiProvidersGeminiEditPage() {
const modelDiscoveryRequestIdRef = useRef(0);
const hasIndexParam = typeof params.index === 'string';
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
const invalidIndexParam = hasIndexParam && editIndex === null;
const initialData = useMemo(() => {
+2 -7
View File
@@ -10,6 +10,7 @@ import type { ApiKeyEntry, OpenAIProviderConfig } from '@/types';
import type { ModelInfo } from '@/utils/models';
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
import { areKeyValueEntriesEqual, areModelEntriesEqual } from '@/utils/compare';
import { parseRouteIndexParam } from '@/utils/routeParams';
import { buildApiKeyEntry } from '@/components/providers/utils';
import type { ModelEntry, OpenAIFormState } from '@/components/providers/types';
import type { KeyTestStatus, OpenAIEditBaseline } from '@/stores/useOpenAIEditDraftStore';
@@ -52,12 +53,6 @@ const buildEmptyForm = (): OpenAIFormState => ({
testModel: undefined,
});
const parseIndexParam = (value: string | undefined) => {
if (!value) return null;
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) ? parsed : null;
};
const getErrorMessage = (err: unknown) => {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
@@ -140,7 +135,7 @@ export function AiProvidersOpenAIEditLayout() {
const params = useParams<{ index?: string }>();
const hasIndexParam = typeof params.index === 'string';
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
const invalidIndexParam = hasIndexParam && editIndex === null;
const connectionStatus = useAuthStore((state) => state.connectionStatus);
+2 -7
View File
@@ -16,6 +16,7 @@ import type { ProviderKeyConfig } from '@/types';
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } from '@/utils/compare';
import { parseRouteIndexParam } from '@/utils/routeParams';
import type { VertexFormState } from '@/components/providers';
import layoutStyles from './AiProvidersEditLayout.module.scss';
@@ -33,12 +34,6 @@ const buildEmptyForm = (): VertexFormState => ({
excludedText: '',
});
const parseIndexParam = (value: string | undefined) => {
if (!value) return null;
const parsed = Number.parseInt(value, 10);
return Number.isFinite(parsed) ? parsed : null;
};
const normalizeModelEntries = (entries: Array<{ name: string; alias: string }>) =>
(entries ?? []).reduce<Array<{ name: string; alias: string }>>((acc, entry) => {
const name = String(entry?.name ?? '').trim();
@@ -93,7 +88,7 @@ export function AiProvidersVertexEditPage() {
const [baseline, setBaseline] = useState(() => buildVertexBaseline(buildEmptyForm()));
const hasIndexParam = typeof params.index === 'string';
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
const invalidIndexParam = hasIndexParam && editIndex === null;
const initialData = useMemo(() => {
+10
View File
@@ -0,0 +1,10 @@
const ROUTE_INDEX_PATTERN = /^(0|[1-9]\d*)$/;
export function parseRouteIndexParam(value: string | undefined): number | null {
// Route indexes must be canonical digits only; parseInt would accept values like "1abc"
// and could route a malformed URL to a real provider entry.
if (!value || !ROUTE_INDEX_PATTERN.test(value)) return null;
const parsed = Number(value);
return Number.isSafeInteger(parsed) ? parsed : null;
}