This is an automated email from the ASF dual-hosted git repository.
fanjia pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 745b56de4e [Fix][Connector-V2] Fixed the problem of complex path
reading of ftp (#9781)
745b56de4e is described below
commit 745b56de4e3fa479100cdcc2adefe2ade31b5760
Author: corgy-w <[email protected]>
AuthorDate: Sun Oct 19 16:41:00 2025 +0800
[Fix][Connector-V2] Fixed the problem of complex path reading of ftp (#9781)
---
docs/en/connector-v2/source/FtpFile.md | 9 +++
docs/zh/connector-v2/source/FtpFile.md | 8 ++
.../seatunnel/file/ftp/config/FtpConf.java | 2 +
.../file/ftp/config/FtpFileBaseOptions.java | 6 ++
.../file/ftp/sink/FtpFileSinkFactory.java | 1 +
.../file/ftp/source/FtpFileSourceFactory.java | 1 +
.../file/ftp/system/SeaTunnelFTPFileSystem.java | 5 ++
.../e2e/connector/file/ftp/FtpFileIT.java | 51 ++++++++++++
.../ftp_special_characters_path_to_assert.conf | 91 ++++++++++++++++++++++
9 files changed, 174 insertions(+)
diff --git a/docs/en/connector-v2/source/FtpFile.md
b/docs/en/connector-v2/source/FtpFile.md
index f292a7eb3d..cd1807043b 100644
--- a/docs/en/connector-v2/source/FtpFile.md
+++ b/docs/en/connector-v2/source/FtpFile.md
@@ -280,6 +280,15 @@ The target ftp connection mode , default is active mode,
supported as the follow
`active_local` `passive_local`
+### control_encoding [string]
+
+Character encoding for FTP control connection. Default is `UTF-8`.
+
+When file paths contain special characters (such as `$`, spaces, Chinese
characters, etc.),
+this should be set to `UTF-8` to ensure paths can be parsed correctly.
+
+For example: `/data/whale_ops/share/$Fund-Product/DA - SANY
(三一)/Daily/2025.08.18/file.xlsx`
+
### delimiter/field_delimiter [string]
**delimiter** parameter will deprecate after version 2.3.5, please use
**field_delimiter** instead.
diff --git a/docs/zh/connector-v2/source/FtpFile.md
b/docs/zh/connector-v2/source/FtpFile.md
index ee6d3ab325..f7272f45a2 100644
--- a/docs/zh/connector-v2/source/FtpFile.md
+++ b/docs/zh/connector-v2/source/FtpFile.md
@@ -257,6 +257,14 @@ markdown 解析器提取各种元素,包括标题、段落、列表、代码
`active_local` `passive_local`
+### control_encoding [string]
+
+FTP 控制连接的字符编码。默认为 `UTF-8`。
+
+当文件路径包含特殊字符(如 `$`、空格、中文字符等)时,需要设置为 `UTF-8` 以确保路径能够正确解析。
+
+例如:`/data/whale_ops/share/$Fund-Product/DA - SANY
(三一)/Daily/2025.08.18/file.xlsx`
+
### delimiter/field_delimiter [string]
**delimiter** 参数将在 2.3.5 版本后弃用,请使用 **field_delimiter** 代替。
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConf.java
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConf.java
index 3b8360b15f..aba74db3bd 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConf.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConf.java
@@ -61,6 +61,8 @@ public class FtpConf extends HadoopConf {
ftpOptions.put(
"fs.ftp.remote.verification.enabled",
String.valueOf(config.get(FtpFileBaseOptions.FTP_REMOTE_VERIFICATION_ENABLED)));
+ ftpOptions.put(
+ "fs.ftp.control.encoding",
config.get(FtpFileBaseOptions.FTP_CONTROL_ENCODING));
hadoopConf.setExtraOptions(ftpOptions);
return hadoopConf;
}
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpFileBaseOptions.java
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpFileBaseOptions.java
index e0ca9e977c..1ad33259b4 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpFileBaseOptions.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpFileBaseOptions.java
@@ -50,4 +50,10 @@ public class FtpFileBaseOptions extends FileBaseOptions {
.defaultValue(true)
.withDescription(
"Whether to enable remote host verification for
FTP data channels (enabled by default)");
+ public static final Option<String> FTP_CONTROL_ENCODING =
+ Options.key("control_encoding")
+ .stringType()
+ .defaultValue("UTF-8")
+ .withDescription(
+ "Character encoding for FTP control connection.
Use UTF-8 to support special characters in file paths");
}
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSinkFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSinkFactory.java
index 38d578fdff..7ad7cd9c84 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSinkFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSinkFactory.java
@@ -122,6 +122,7 @@ public class FtpFileSinkFactory extends
BaseMultipleTableFileSinkFactory {
.optional(FileBaseSinkOptions.CREATE_EMPTY_FILE_WHEN_NO_DATA)
.optional(FileBaseSinkOptions.FILENAME_EXTENSION)
.optional(FtpFileSourceOptions.FTP_REMOTE_VERIFICATION_ENABLED)
+ .optional(FtpFileSourceOptions.FTP_CONTROL_ENCODING)
.build();
}
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
index e0df0cbc2f..3dfcbadc77 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
@@ -98,6 +98,7 @@ public class FtpFileSourceFactory implements
TableSourceFactory {
.optional(FileBaseSourceOptions.FILENAME_EXTENSION)
.optional(FileBaseSourceOptions.READ_COLUMNS)
.optional(FtpFileSourceOptions.FTP_REMOTE_VERIFICATION_ENABLED)
+ .optional(FtpFileSourceOptions.FTP_CONTROL_ENCODING)
.build();
}
diff --git
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java
index 646aecda1f..c6bb9c8aed 100644
---
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java
+++
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java
@@ -70,6 +70,7 @@ public class SeaTunnelFTPFileSystem extends FileSystem {
public static final String FS_FTP_CONNECTION_MODE =
"fs.ftp.connection.mode";
public static final String FS_FTP_REMOTE_VERIFICATION_ENABLED =
"fs.ftp.remote.verification.enabled";
+ public static final String FS_FTP_CONTROL_ENCODING =
"fs.ftp.control.encoding";
public static final String E_SAME_DIRECTORY_ONLY = "only same directory
renames are supported";
@@ -137,6 +138,10 @@ public class SeaTunnelFTPFileSystem extends FileSystem {
String connectionMode =
conf.get(FS_FTP_CONNECTION_MODE,
FtpConnectionMode.ACTIVE_LOCAL.getMode());
+ // Set control encoding BEFORE connecting - this is critical for
special characters
+ String controlEncoding = conf.get(FS_FTP_CONTROL_ENCODING, "UTF-8");
+ client.setControlEncoding(controlEncoding);
+
// Check if remote verification is enabled
boolean remoteVerificationEnabled =
conf.getBoolean(
diff --git
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-ftp-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/ftp/FtpFileIT.java
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-ftp-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/ftp/FtpFileIT.java
index 1f20491b88..95b3a408e4 100644
---
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-ftp-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/ftp/FtpFileIT.java
+++
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-ftp-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/ftp/FtpFileIT.java
@@ -257,6 +257,57 @@ public class FtpFileIT extends TestSuiteBase implements
TestResource {
Assertions.assertEquals(getFileListFromContainer(homePath +
sink02).size(), 1);
}
+ @TestTemplate
+ public void testFtpFileWithSpecialCharactersPath(TestContainer container)
+ throws IOException, InterruptedException {
+ TestHelper helper = new TestHelper(container);
+
+ // Create test file with spaces in path - simpler test to avoid Docker
memory issues
+ String specialPath = "/tmp/seatunnel/test spaces";
+ String fileName = "file with spaces.txt";
+ String fullPath = specialPath + "/" + fileName;
+ String homePath = "/home/vsftpd/seatunnel";
+ String containerPath = homePath + fullPath;
+
+ try {
+ // Create directory structure with special characters
+ Container.ExecResult mkdirResult =
+ ftpContainer.execInContainer("mkdir", "-p", homePath +
specialPath);
+ log.info(
+ "mkdir result: exit code {}, stdout: {}, stderr: {}",
+ mkdirResult.getExitCode(),
+ mkdirResult.getStdout(),
+ mkdirResult.getStderr());
+
+ // Create test file with content
+ String testContent = "name,age,city\nJohn,30,NYC\nJane,25,LA\n";
+ Container.ExecResult createResult =
+ ftpContainer.execInContainer(
+ "sh", "-c", "echo '" + testContent + "' > '" +
containerPath + "'");
+ log.info(
+ "create file result: exit code {}, stdout: {}, stderr: {}",
+ createResult.getExitCode(),
+ createResult.getStdout(),
+ createResult.getStderr());
+
+ // Verify file was created
+ Container.ExecResult lsResult =
+ ftpContainer.execInContainer("ls", "-la", containerPath);
+ Assertions.assertEquals(
+ 0,
+ lsResult.getExitCode(),
+ "Failed to create test file with special characters: " +
lsResult.getStderr());
+ log.info("File created successfully: {}", lsResult.getStdout());
+
+ // Test reading file with special characters in path using UTF-8
control encoding
+ helper.execute("/text/ftp_special_characters_path_to_assert.conf");
+
+ } finally {
+ // Clean up
+ deleteFileFromContainer(homePath + "/tmp/seatunnel/test\\ spaces");
+ }
+ }
+
@TestTemplate
public void testMultipleTableAndSaveMode(TestContainer container)
throws IOException, InterruptedException {
diff --git
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-ftp-e2e/src/test/resources/text/ftp_special_characters_path_to_assert.conf
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-ftp-e2e/src/test/resources/text/ftp_special_characters_path_to_assert.conf
new file mode 100644
index 0000000000..b728465aa6
--- /dev/null
+++
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-ftp-e2e/src/test/resources/text/ftp_special_characters_path_to_assert.conf
@@ -0,0 +1,91 @@
+#
+# 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.
+#
+
+env {
+ parallelism = 1
+ job.mode = "BATCH"
+}
+
+source {
+ FtpFile {
+ host = "ftp"
+ port = 21
+ user = seatunnel
+ password = pass
+ # Test path with spaces
+ path = """/tmp/seatunnel/test spaces/file with spaces.txt"""
+ file_format_type = "text"
+ # Key configuration: UTF-8 control encoding to support special characters
in paths
+ control_encoding = "UTF-8"
+ plugin_output = "ftp"
+ schema = {
+ fields {
+ name = string
+ age = int
+ city = string
+ }
+ }
+ field_delimiter = ","
+ skip_header_row_number = 1
+ }
+}
+
+sink {
+ Assert {
+ rules {
+ row_rules = [
+ {
+ rule_type = MAX_ROW
+ rule_value = 2
+ },
+ {
+ rule_type = MIN_ROW
+ rule_value = 2
+ }
+ ],
+ field_rules = [
+ {
+ field_name = name
+ field_type = string
+ field_value = [
+ {
+ rule_type = NOT_NULL
+ }
+ ]
+ },
+ {
+ field_name = age
+ field_type = int
+ field_value = [
+ {
+ rule_type = NOT_NULL
+ }
+ ]
+ },
+ {
+ field_name = city
+ field_type = string
+ field_value = [
+ {
+ rule_type = NOT_NULL
+ }
+ ]
+ }
+ ]
+ }
+ }
+}