feat(web): make markdown form field name length configurable (#41697)

This commit is contained in:
Wu Tianwei 2026-09-03 03:21:10 +00:00 committed by GitHub
parent 6536ffc422
commit b0b495d243
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 110 additions and 2 deletions

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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<typeof import('@/config')>('@/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<typeof MarkdownForm>['node']
render(<MarkdownForm node={node} />)
expect(screen.getByPlaceholderText('within-limit')).toBeInTheDocument()
expect(screen.queryByPlaceholderText('above-limit')).not.toBeInTheDocument()
})
})

View File

@ -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 (

View File

@ -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

View File

@ -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}

View File

@ -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'),