From b0b495d243a122e149be59db9ca8308d72b91c32 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:21:10 +0000 Subject: [PATCH] feat(web): make markdown form field name length configurable (#41697) --- docker/envs/core-services/web.env.example | 2 + web/.env.example | 2 + web/__tests__/env.spec.ts | 37 +++++++++++++ .../form-field-name-max-length.spec.tsx | 52 +++++++++++++++++++ .../components/base/markdown-blocks/form.tsx | 9 +++- web/config/index.ts | 2 + web/docker/entrypoint.sh | 1 + web/env.ts | 7 +++ 8 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 web/app/components/base/markdown-blocks/__tests__/form-field-name-max-length.spec.tsx diff --git a/docker/envs/core-services/web.env.example b/docker/envs/core-services/web.env.example index d4366bca7d7..bafb965778a 100644 --- a/docker/envs/core-services/web.env.example +++ b/docker/envs/core-services/web.env.example @@ -16,6 +16,8 @@ WORKFLOW_GENERATION_TIMEOUT_MS=180000 ALLOW_INLINE_STYLES=false # Example: ()!*&()!*&-。.;;+=— MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS= +# Maximum length of Markdown form field names. +MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH=128 ALLOW_UNSAFE_DATA_SCHEME=false MAX_TREE_DEPTH=50 MARKETPLACE_API_URL=https://marketplace.dify.ai diff --git a/web/.env.example b/web/.env.example index db3928f6312..4433c3282b4 100644 --- a/web/.env.example +++ b/web/.env.example @@ -66,6 +66,8 @@ NEXT_PUBLIC_ALLOW_INLINE_STYLES=false # Additional literal characters allowed in Markdown form field names. # Example: ()!*&()!*&-。.;;+=— NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS= +# Maximum length of Markdown form field names. +NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH=128 # Allow rendering unsafe URLs which have "data:" scheme. NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME=false diff --git a/web/__tests__/env.spec.ts b/web/__tests__/env.spec.ts index 4baf8123b55..f196758970f 100644 --- a/web/__tests__/env.spec.ts +++ b/web/__tests__/env.spec.ts @@ -2,6 +2,8 @@ describe('env runtime transport', () => { const originalAgentV2Env = process.env.NEXT_PUBLIC_ENABLE_AGENT_V2 const originalMarkdownFormFieldNameExtraChars = process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS + const originalMarkdownFormFieldNameMaxLength = + process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH const originalTurnstileSiteKey = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY beforeEach(() => { @@ -11,9 +13,11 @@ describe('env runtime transport', () => { document.body.removeAttribute('data-enable-agent-v2') document.body.removeAttribute('data-enable-agent-v-2') document.body.removeAttribute('data-markdown-form-field-name-extra-chars') + document.body.removeAttribute('data-markdown-form-field-name-max-length') document.body.removeAttribute('data-turnstile-site-key') delete process.env.NEXT_PUBLIC_ENABLE_AGENT_V2 delete process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS + delete process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH delete process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY }) @@ -25,6 +29,11 @@ describe('env runtime transport', () => { else process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS = originalMarkdownFormFieldNameExtraChars + if (originalMarkdownFormFieldNameMaxLength === undefined) + delete process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH + else + process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH = + originalMarkdownFormFieldNameMaxLength if (originalTurnstileSiteKey === undefined) delete process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY else process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY = originalTurnstileSiteKey }) @@ -74,6 +83,34 @@ describe('env runtime transport', () => { expect(datasetMap['data-markdown-form-field-name-extra-chars']).toBe('()!*&()!*&-') }) + it('should default the Markdown form field name maximum length to 128', async () => { + const { env } = await import('../env') + + expect(env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH).toBe(128) + }) + + it('should read the Markdown form field name maximum length from the browser runtime dataset', async () => { + document.body.setAttribute('data-markdown-form-field-name-max-length', '64') + + const { env } = await import('../env') + + expect(env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH).toBe(64) + }) + + it('should emit the Markdown form field name maximum length in the server runtime dataset', async () => { + process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH = '64' + + vi.doMock('../utils/client', () => ({ + isClient: false, + isServer: true, + })) + + const { getDatasetMap } = await import('../env') + const datasetMap = getDatasetMap() + + expect(datasetMap['data-markdown-form-field-name-max-length']).toBe(64) + }) + it('should read the Turnstile site key from the browser runtime dataset', async () => { document.body.setAttribute('data-turnstile-site-key', 'site-key-for-tests') diff --git a/web/app/components/base/markdown-blocks/__tests__/form-field-name-max-length.spec.tsx b/web/app/components/base/markdown-blocks/__tests__/form-field-name-max-length.spec.tsx new file mode 100644 index 00000000000..dcfbe101d71 --- /dev/null +++ b/web/app/components/base/markdown-blocks/__tests__/form-field-name-max-length.spec.tsx @@ -0,0 +1,52 @@ +import type { ComponentProps } from 'react' +import { render, screen } from '@testing-library/react' +import MarkdownForm from '../form' + +vi.mock('@/app/components/base/chat/chat/context', () => ({ + useChatContext: () => ({}), +})) + +vi.mock('@/config', async () => { + const actual = await vi.importActual('@/config') + return { + ...actual, + MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH: 4, + } +}) + +describe('MarkdownForm field name maximum length', () => { + it('should render a field at the configured limit and reject one above it', () => { + const node = { + type: 'element', + tagName: 'form', + properties: {}, + children: [ + { + type: 'element', + tagName: 'input', + properties: { + type: 'text', + name: 'abcd', + placeholder: 'within-limit', + }, + children: [], + }, + { + type: 'element', + tagName: 'input', + properties: { + type: 'text', + name: 'abcde', + placeholder: 'above-limit', + }, + children: [], + }, + ], + } satisfies ComponentProps['node'] + + render() + + expect(screen.getByPlaceholderText('within-limit')).toBeInTheDocument() + expect(screen.queryByPlaceholderText('above-limit')).not.toBeInTheDocument() + }) +}) diff --git a/web/app/components/base/markdown-blocks/form.tsx b/web/app/components/base/markdown-blocks/form.tsx index 2e8dd7921ef..eab848e9203 100644 --- a/web/app/components/base/markdown-blocks/form.tsx +++ b/web/app/components/base/markdown-blocks/form.tsx @@ -21,7 +21,7 @@ import { toDayjs, } from '@/app/components/base/date-and-time-picker/utils/dayjs' import Input from '@/app/components/base/input' -import { MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS } from '@/config' +import { MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS, MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH } from '@/config' import { getMarkdownButtonAppearance } from './button-appearance' const DATA_FORMAT = { @@ -66,7 +66,12 @@ const EXTRA_SAFE_NAME_CHARS = new Set(MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS) const PROTOTYPE_POISON_KEYS = new Set(['__proto__', 'constructor', 'prototype']) function isSafeName(name: unknown): name is string { - if (typeof name !== 'string' || name.length === 0 || name.length > 128) return false + if ( + typeof name !== 'string' || + name.length === 0 || + name.length > MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH + ) + return false const [firstChar, ...remainingChars] = Array.from(name) return ( diff --git a/web/config/index.ts b/web/config/index.ts index de2d01ff287..f6e2883c6d0 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -271,6 +271,8 @@ export const MAX_ITERATIONS_NUM = env.NEXT_PUBLIC_MAX_ITERATIONS_NUM export const MAX_TREE_DEPTH = env.NEXT_PUBLIC_MAX_TREE_DEPTH export const MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS = env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS +export const MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH = + env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH export const ALLOW_INLINE_STYLES = env.NEXT_PUBLIC_ALLOW_INLINE_STYLES export const ALLOW_UNSAFE_DATA_SCHEME = env.NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME diff --git a/web/docker/entrypoint.sh b/web/docker/entrypoint.sh index ea4f0b57bd3..4c0918f3043 100755 --- a/web/docker/entrypoint.sh +++ b/web/docker/entrypoint.sh @@ -36,6 +36,7 @@ export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST} export NEXT_PUBLIC_ALLOW_EMBED=${ALLOW_EMBED} export NEXT_PUBLIC_ALLOW_INLINE_STYLES=${ALLOW_INLINE_STYLES:-false} export NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS="${NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS:-${MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS:-}}" +export NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH="${NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH:-${MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH:-}}" export NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME=${ALLOW_UNSAFE_DATA_SCHEME:-false} export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE} export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH} diff --git a/web/env.ts b/web/env.ts index 229d34fbcb9..a36abbd9943 100644 --- a/web/env.ts +++ b/web/env.ts @@ -100,6 +100,10 @@ const clientSchema = { * Additional literal characters allowed in Markdown form field names. */ NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS: z.string().default(''), + /** + * Maximum length of Markdown form field names. + */ + NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH: coercedNumber.default(128), /** * The API PREFIX for MARKETPLACE */ @@ -260,6 +264,9 @@ export const env = createEnv({ NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS: isServer ? process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_EXTRA_CHARS : getRuntimeEnvFromBody('markdownFormFieldNameExtraChars'), + NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH: isServer + ? process.env.NEXT_PUBLIC_MARKDOWN_FORM_FIELD_NAME_MAX_LENGTH + : getRuntimeEnvFromBody('markdownFormFieldNameMaxLength'), NEXT_PUBLIC_MARKETPLACE_API_PREFIX: isServer ? process.env.NEXT_PUBLIC_MARKETPLACE_API_PREFIX : getRuntimeEnvFromBody('marketplaceApiPrefix'),