From efa6f9b9fa4d8cc14f0db233cd9034037ee134cd Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Mon, 1 Dec 2025 15:48:00 +0800 Subject: [PATCH] perf: Multi-domain validation in embedded systems uses semicolon delimiters --- backend/common/utils/utils.py | 12 ++++-- frontend/src/i18n/en.json | 4 +- frontend/src/i18n/ko-KR.json | 4 +- frontend/src/i18n/zh-CN.json | 4 +- frontend/src/views/system/embedded/Page.vue | 42 +++++++++---------- frontend/src/views/system/embedded/iframe.vue | 34 ++++++++------- 6 files changed, 56 insertions(+), 44 deletions(-) diff --git a/backend/common/utils/utils.py b/backend/common/utils/utils.py index 8d3fc808..ec8c0d85 100644 --- a/backend/common/utils/utils.py +++ b/backend/common/utils/utils.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta, timezone from logging.handlers import RotatingFileHandler from pathlib import Path +import re from urllib.parse import urlparse from fastapi import Request @@ -266,9 +267,14 @@ def get_origin_from_referer(request: Request): def origin_match_domain(origin: str, domain: str) -> bool: if not origin or not domain: return False - origin_text = origin.rstrip('/') - domain_list = domain.replace(" ", "").split(',') - return origin_text in [d.rstrip('/') for d in domain_list] + origin_normalized = origin.rstrip('/') + + for d in re.split(r'[,;]', domain): + if d.strip().rstrip('/') == origin_normalized: + return True + + return False + def equals_ignore_case(str1: str, *args: str) -> bool: if str1 is None: diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 88857d2c..259afd4b 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -577,7 +577,7 @@ "application_name": "Application name", "application_description": "Application description", "cross_domain_settings": "Cross-domain settings", - "third_party_address": "Please enter the embedded third party address", + "third_party_address": "Please enter the embedded third party address,multiple items separated by semicolons", "set_to_private": "Set as private", "set_to_public": "Set as public", "public": "Public", @@ -586,7 +586,7 @@ "configure_interface": "Configure interface", "interface_url": "Interface URL", "format_is_incorrect": "format is incorrect{msg}", - "domain_format_incorrect": ",start with http/https, no trailing slash (/), multiple domains separated by half-width commas (,)", + "domain_format_incorrect": ", start with http:// or https://, no trailing slash (/), multiple domains separated by semicolons", "interface_url_incorrect": ",enter a relative path starting with /", "aes_enable": "Enable AES encryption", "aes_enable_tips": "The fields (host, user, password, dataBase, schema) are all encrypted using the AES-CBC-PKCS5Padding encryption method", diff --git a/frontend/src/i18n/ko-KR.json b/frontend/src/i18n/ko-KR.json index b86b6252..a79aa92e 100644 --- a/frontend/src/i18n/ko-KR.json +++ b/frontend/src/i18n/ko-KR.json @@ -577,7 +577,7 @@ "application_name": "애플리케이션 이름", "application_description": "애플리케이션 설명", "cross_domain_settings": "교차 도메인 설정", - "third_party_address": "임베디드할 제3자 주소를 입력하십시오", + "third_party_address": "임베디드할 제3자 주소를 입력하십시오, 여러 항목을 세미콜론으로 구분", "set_to_private": "비공개로 설정", "set_to_public": "공개로 설정", "public": "공개", @@ -585,7 +585,7 @@ "configure_interface": "인터페이스 설정", "interface_url": "인터페이스 URL", "format_is_incorrect": "형식이 올바르지 않습니다{msg}", - "domain_format_incorrect": ", http/https로 시작, 슬래시(/)로 끝나지 않음, 여러 도메인은 반각 쉼표(,)로 구분", + "domain_format_incorrect": ", http:// 또는 https://로 시작해야 하며, 슬래시(/)로 끝날 수 없습니다. 여러 도메인은 세미콜론으로 구분합니다", "interface_url_incorrect": ", 상대 경로를 입력해주세요. /로 시작합니다", "aes_enable": "AES 암호화 활성화", "aes_enable_tips": "암호화 필드 (host, user, password, dataBase, schema)는 모두 AES-CBC-PKCS5Padding 암호화 방식을 사용합니다", diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index fa93317e..65917ec8 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -577,7 +577,7 @@ "application_name": "应用名称", "application_description": "应用描述", "cross_domain_settings": "跨域设置", - "third_party_address": "请输入嵌入的第三方地址", + "third_party_address": "请输入嵌入的第三方地址,多个以分号分割", "set_to_private": "设为私有", "set_to_public": "设为公共", "public": "公共", @@ -585,7 +585,7 @@ "configure_interface": "配置接口", "interface_url": "接口 URL", "format_is_incorrect": "格式不对{msg}", - "domain_format_incorrect": ",http或https开头,不能以 / 结尾,多个域名以逗号(半角)分隔", + "domain_format_incorrect": ",http或https开头,不能以 / 结尾,多个域名以分号(半角)分隔", "interface_url_incorrect": ",请填写相对路径,以/开头", "aes_enable": "开启 AES 加密", "aes_enable_tips": "加密字段 (host, user, password, dataBase, schema) 均采用 AES-CBC-PKCS5Padding 加密方式", diff --git a/frontend/src/views/system/embedded/Page.vue b/frontend/src/views/system/embedded/Page.vue index 42784bdc..d14024e4 100644 --- a/frontend/src/views/system/embedded/Page.vue +++ b/frontend/src/views/system/embedded/Page.vue @@ -198,7 +198,15 @@ const search = () => { searchLoading.value = false }) } - +const splitString = (str: string) => { + if (typeof str !== 'string') { + return [] + } + return str + .split(/[,;]/) + .map((item) => item.trim()) + .filter((item) => item !== '') +} const termFormRef = ref() const validateUrl = (_: any, value: any, callback: any) => { if (value === '') { @@ -209,20 +217,15 @@ const validateUrl = (_: any, value: any, callback: any) => { ) } else { // var Expression = /(https?:\/\/)?([\da-z\.-]+)\.([a-z]{2,6})(:\d{1,5})?([\/\w\.-]*)*\/?(#[\S]+)?/ // eslint-disable-line - value - .trim() - .split(',') - .forEach((tempVal: string) => { - var Expression = /^https?:\/\/[^\s/?#]+(:\d+)?/i - var objExp = new RegExp(Expression) - if (objExp.test(tempVal) && !tempVal.endsWith('/')) { - callback() - } else { - callback( - t('embedded.format_is_incorrect', { msg: t('embedded.domain_format_incorrect') }) - ) - } - }) + splitString(value).forEach((tempVal: string) => { + var Expression = /^https?:\/\/[^\s/?#]+(:\d+)?/i + var objExp = new RegExp(Expression) + if (objExp.test(tempVal) && !tempVal.endsWith('/')) { + callback() + } else { + callback(t('embedded.format_is_incorrect', { msg: t('embedded.domain_format_incorrect') })) + } + }) } } const rules = { @@ -602,13 +605,10 @@ const copyCode = (row: any, key: any = 'app_secret') => { diff --git a/frontend/src/views/system/embedded/iframe.vue b/frontend/src/views/system/embedded/iframe.vue index 61becbde..89ea2052 100644 --- a/frontend/src/views/system/embedded/iframe.vue +++ b/frontend/src/views/system/embedded/iframe.vue @@ -265,6 +265,15 @@ const setUiRef = ref() const handleSetUi = (row: any) => { setUiRef.value.open(row) } +const splitString = (str: string) => { + if (typeof str !== 'string') { + return [] + } + return str + .split(/[,;]/) + .map((item) => item.trim()) + .filter((item) => item !== '') +} const validateUrl = (_: any, value: any, callback: any) => { if (value === '') { callback( @@ -274,20 +283,15 @@ const validateUrl = (_: any, value: any, callback: any) => { ) } else { // var Expression = /(https?:\/\/)?([\da-z\.-]+)\.([a-z]{2,6})(:\d{1,5})?([\/\w\.-]*)*\/?(#[\S]+)?/ // eslint-disable-line - value - .trim() - .split(',') - .forEach((tempVal: string) => { - var Expression = /^https?:\/\/[^\s/?#]+(:\d+)?/i - var objExp = new RegExp(Expression) - if (objExp.test(tempVal) && !tempVal.endsWith('/')) { - callback() - } else { - callback( - t('embedded.format_is_incorrect', { msg: t('embedded.domain_format_incorrect') }) - ) - } - }) + splitString(value).forEach((tempVal: string) => { + var Expression = /^https?:\/\/[^\s/?#]+(:\d+)?/i + var objExp = new RegExp(Expression) + if (objExp.test(tempVal) && !tempVal.endsWith('/')) { + callback() + } else { + callback(t('embedded.format_is_incorrect', { msg: t('embedded.domain_format_incorrect') })) + } + }) } } const rules = { @@ -759,6 +763,8 @@ const saveHandler = () => {