This is an automated email from the ASF dual-hosted git repository.

fanjia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git


The following commit(s) were added to refs/heads/main by this push:
     new ee399d28 [Bug][Seatunnel-web] Escape seatunnel-web placeholders (#225)
ee399d28 is described below

commit ee399d283b05ffc3fd191ca834f5f65928065b80
Author: Mohammad Arshad <[email protected]>
AuthorDate: Sat Oct 12 08:16:53 2024 +0530

    [Bug][Seatunnel-web] Escape seatunnel-web placeholders (#225)
---
 README_CN.md                                       |  4 +-
 .../framework/SeaTunnelOptionRuleWrapper.java      | 21 +++++---
 .../org/apache/seatunnel/app/utils/JobUtils.java   | 19 +++++--
 .../apache/seatunnel/app/utils/JobUtilsTests.java  | 16 ++++++
 .../app/dynamicforms/PlaceholderUtil.java          | 46 +++++++++++++++++
 .../app/dynamicforms/PlaceholderUtilTest.java      | 60 ++++++++++++++++++++++
 6 files changed, 153 insertions(+), 13 deletions(-)

diff --git a/README_CN.md b/README_CN.md
index d3e966c7..79339a94 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -73,7 +73,7 @@ sh build.sh code
 #### 2.4 配置应用程序并运行SeaTunnel Web后端服务器
 
 1. 编辑 `seatunnel-server/seatunnel-app/src/main/resources/application.yml` 
写数据库连接信息
-2. 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 
文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel(注意不要太短)。
+2. 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 
文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel (注意不要太短)。
 
 ![img.png](docs/images/application_config.png)
 
@@ -173,7 +173,7 @@ tar -zxvf apache-seatunnel-web-${project.version}.tar.gz
 #### 3.5 配置应用并运行 SeaTunnel Web 后端服务
 
 * 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 
在文件中填写数据库连接信息和数据服务接口相关信息。
-* 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 
文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel(注意不要太短)。
+* 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 
文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel (注意不要太短)。
 
 ![image](docs/images/application_config.png)
 
diff --git 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java
 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java
index 4f5e7c60..41f307e9 100644
--- 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java
+++ 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java
@@ -27,6 +27,7 @@ import org.apache.seatunnel.app.dynamicforms.FormLocale;
 import org.apache.seatunnel.app.dynamicforms.FormOptionBuilder;
 import org.apache.seatunnel.app.dynamicforms.FormStructure;
 import org.apache.seatunnel.app.dynamicforms.FormStructureBuilder;
+import org.apache.seatunnel.app.dynamicforms.PlaceholderUtil;
 import org.apache.seatunnel.app.dynamicforms.validate.ValidateBuilder;
 import org.apache.seatunnel.common.constants.PluginType;
 
@@ -97,6 +98,17 @@ public class SeaTunnelOptionRuleWrapper {
         return FormOptionSort.sortFormStructure(formStructureBuilder.build());
     }
 
+    private static Object getDefaultValue(Option<?> option) {
+        Object defValue = option.defaultValue();
+        if (defValue == null) {
+            return null;
+        }
+        if (String.class.equals(option.typeReference().getType())) {
+            return PlaceholderUtil.escapePlaceholders(defValue.toString());
+        }
+        return defValue;
+    }
+
     private static List<AbstractFormOption> wrapperOptionOptions(
             @NonNull String connectorName, @NonNull List<Option<?>> 
optionList, FormLocale locale) {
         return optionList.stream()
@@ -401,10 +413,7 @@ public class SeaTunnelOptionRuleWrapper {
         AbstractFormOption abstractFormOption =
                 staticSelectOptionBuilder
                         .formStaticSelectOption()
-                        .withDefaultValue(
-                                option.defaultValue() == null
-                                        ? null
-                                        : option.defaultValue().toString());
+                        .withDefaultValue(getDefaultValue(option));
 
         String placeholderI18nOptionKey = i18nOptionKey + "_description";
         if (enableLabelI18n(connectorName, placeholderI18nOptionKey, locale)) {
@@ -457,7 +466,7 @@ public class SeaTunnelOptionRuleWrapper {
                 builder.withField(option.key())
                         .inputOptionBuilder()
                         .formTextInputOption()
-                        .withDefaultValue(option.defaultValue());
+                        .withDefaultValue(getDefaultValue(option));
         if (enableLabelI18n(connectorName, placeholderI18nOptionKey, locale)) {
             abstractFormOption = 
abstractFormOption.withI18nPlaceholder(placeholderI18nOptionKey);
         } else {
@@ -484,7 +493,7 @@ public class SeaTunnelOptionRuleWrapper {
                         .inputOptionBuilder()
                         .formTextareaInputOption()
                         .withClearable()
-                        .withDefaultValue(option.defaultValue());
+                        .withDefaultValue(getDefaultValue(option));
         if (enableLabelI18n(connectorName, placeholderI18nOptionKey, locale)) {
             abstractFormOption = 
abstractFormOption.withI18nPlaceholder(placeholderI18nOptionKey);
         } else {
diff --git 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java
 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java
index b51db986..bf0f36d3 100644
--- 
a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java
+++ 
b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java
@@ -32,7 +32,8 @@ public class JobUtils {
 
     // The maximum length of the job execution error message, 4KB
     private static final int ERROR_MESSAGE_MAX_LENGTH = 4096;
-    private static final Pattern placeholderPattern = 
Pattern.compile("\\$\\{(\\w+)(?::(.*?))?\\}");
+    private static final Pattern placeholderPattern =
+            Pattern.compile("(\\\\{0,2})\\$\\{(\\w+)(?::(.*?))?\\}");
 
     public static String getJobInstanceErrorMessage(String message) {
         if (message == null) {
@@ -75,18 +76,26 @@ public class JobUtils {
                 (jobExecParam != null && jobExecParam.getPlaceholderValues() 
!= null)
                         ? jobExecParam.getPlaceholderValues()
                         : Collections.emptyMap();
-
         Matcher matcher = placeholderPattern.matcher(jobConfigString);
         StringBuffer result = new StringBuffer();
 
         while (matcher.find()) {
-            String placeholderName = matcher.group(1);
-            String replacement = 
placeholderValues.getOrDefault(placeholderName, matcher.group(2));
+            String escapeCharacter = matcher.group(1);
+            String placeholderName = matcher.group(2);
+
+            if (escapeCharacter != null && !escapeCharacter.isEmpty()) {
+                String withoutEscape =
+                        matcher.group().replace("\\\\${", 
"${").replace("\\${", "${");
+                matcher.appendReplacement(result, 
Matcher.quoteReplacement(withoutEscape));
+                // remove the escape character and continue
+                continue;
+            }
+            String replacement = 
placeholderValues.getOrDefault(placeholderName, matcher.group(3));
             if (replacement == null) {
                 throw new SeatunnelException(
                         SeatunnelErrorEnum.JOB_NO_VALUE_FOUND_FOR_PLACEHOLDER, 
placeholderName);
             }
-            matcher.appendReplacement(result, replacement);
+            matcher.appendReplacement(result, 
Matcher.quoteReplacement(replacement));
         }
 
         matcher.appendTail(result);
diff --git 
a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java
 
b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java
index dca59073..1d7f2725 100644
--- 
a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java
+++ 
b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java
@@ -109,6 +109,22 @@ public class JobUtilsTests {
         assertNotNull(config);
     }
 
+    @Test
+    public void testEscapedPlaceholderValuesNotReplaced() {
+        String jobConfigContent =
+                
"job.mode=\\${jobModeParam:BATCH}\ncheckpoint.interval=\\\\${checkParam:30}\njob.name=${jobNameParam}";
+        Map<String, String> paramValues = new HashMap<>();
+        paramValues.put("jobModeParam", "STREAMING");
+        paramValues.put("jobNameParam", "newJob");
+        JobExecParam jobExecParam = getJobExecParam(paramValues);
+
+        String expected =
+                
"job.mode=${jobModeParam:BATCH}\ncheckpoint.interval=${checkParam:30}\njob.name=newJob";
+        String actual = 
JobUtils.replaceJobConfigPlaceholders(jobConfigContent, jobExecParam);
+
+        assertEquals(expected, actual);
+    }
+
     private JobExecParam getJobExecParam(Map<String, String> paramValues) {
         JobExecParam jobExecParam = new JobExecParam();
         jobExecParam.setPlaceholderValues(paramValues);
diff --git 
a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtil.java
 
b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtil.java
new file mode 100644
index 00000000..c38209c1
--- /dev/null
+++ 
b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtil.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seatunnel.app.dynamicforms;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PlaceholderUtil {
+
+    private static final Pattern placeholderPattern =
+            Pattern.compile("(?<!\\\\)\\$\\{(\\w+)(?::(.*?))?\\}");
+
+    /**
+     * To replace ${paramName:defaultValue} or ${paramName} with 
\${paramName:defaultValue} and
+     * \${paramName} respectively.
+     *
+     * @param input the input string
+     * @return the input string with placeholders escaped
+     */
+    public static String escapePlaceholders(String input) {
+        Matcher matcher = placeholderPattern.matcher(input);
+        StringBuffer result = new StringBuffer();
+
+        while (matcher.find()) {
+            String placeholder = matcher.group();
+            matcher.appendReplacement(result, Matcher.quoteReplacement("\\" + 
placeholder));
+        }
+
+        matcher.appendTail(result);
+        return result.toString();
+    }
+}
diff --git 
a/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtilTest.java
 
b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtilTest.java
new file mode 100644
index 00000000..4c88c7ac
--- /dev/null
+++ 
b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtilTest.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seatunnel.app.dynamicforms;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PlaceholderUtilTest {
+
+    @Test
+    public void testEscapePlaceholders() {
+        String input = "This is a test string with ${paramName:defaultValue} 
and ${paramName}.";
+        String expectedOutput =
+                "This is a test string with \\${paramName:defaultValue} and 
\\${paramName}.";
+        String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+        assertEquals(expectedOutput, actualOutput);
+    }
+
+    @Test
+    public void testEscapePlaceholdersWithNoPlaceholders() {
+        String input = "This is a test string with no placeholders.";
+        String expectedOutput = "This is a test string with no placeholders.";
+        String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+        assertEquals(expectedOutput, actualOutput);
+    }
+
+    @Test
+    public void testEscapePlaceholdersWithEscapedPlaceholders() {
+        String input = "This is a test string with \\${paramName:defaultValue} 
and \\${paramName}.";
+        String expectedOutput =
+                "This is a test string with \\${paramName:defaultValue} and 
\\${paramName}.";
+        String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+        assertEquals(expectedOutput, actualOutput);
+    }
+
+    @Test
+    public void testEscapePlaceholdersWithMixedPlaceholders() {
+        String input =
+                "This is a test string with ${paramName:defaultValue}, 
\\${paramName}, and ${anotherParam}.";
+        String expectedOutput =
+                "This is a test string with \\${paramName:defaultValue}, 
\\${paramName}, and \\${anotherParam}.";
+        String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+        assertEquals(expectedOutput, actualOutput);
+    }
+}

Reply via email to