diff --git a/weed/s3api/s3api_tables_rest_validation_test.go b/weed/s3api/s3api_tables_rest_validation_test.go index 38305ea6d..d248a5bfb 100644 --- a/weed/s3api/s3api_tables_rest_validation_test.go +++ b/weed/s3api/s3api_tables_rest_validation_test.go @@ -21,7 +21,7 @@ func TestBuildListTablesRequestRejectsInvalidNamespaceQuery(t *testing.T) { namespace string }{ {name: "uppercase", namespace: "InvalidNamespace"}, - {name: "hyphen", namespace: "invalid-ns"}, + {name: "leading hyphen", namespace: "-invalid"}, {name: "slash", namespace: "a/b"}, } @@ -47,7 +47,7 @@ func TestBuildGetTableRequestRejectsInvalidNamespaceQuery(t *testing.T) { namespace string }{ {name: "uppercase", namespace: "InvalidNamespace"}, - {name: "hyphen", namespace: "invalid-ns"}, + {name: "leading hyphen", namespace: "-invalid"}, {name: "slash", namespace: "a/b"}, } @@ -72,7 +72,7 @@ func TestHandleRestOperationReturnsBadRequestForInvalidNamespaceQuery(t *testing namespace string }{ {name: "uppercase", namespace: "InvalidNamespace"}, - {name: "hyphen", namespace: "invalid-ns"}, + {name: "leading hyphen", namespace: "-invalid"}, {name: "slash", namespace: "a/b"}, } diff --git a/weed/s3api/s3tables/handler_name_charset_test.go b/weed/s3api/s3tables/handler_name_charset_test.go new file mode 100644 index 000000000..6ea5af502 --- /dev/null +++ b/weed/s3api/s3tables/handler_name_charset_test.go @@ -0,0 +1,37 @@ +package s3tables + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNameValidationAllowsHyphens(t *testing.T) { + // Hyphen interior is accepted (Iceberg REST clients use names like these). + require.NoError(t, validateNamespacePart("rest-integration-test")) + if _, err := validateTableName("test-create-table"); err != nil { + t.Fatalf("hyphenated table name rejected: %v", err) + } + + // A leading or trailing hyphen on a namespace is still rejected (must start + // and end with a letter or digit), as are characters outside the charset. + require.Error(t, validateNamespacePart("-leading")) + require.Error(t, validateNamespacePart("trailing-")) + require.Error(t, validateNamespacePart("Upper")) + + // Table names must still start with a letter or digit and stay in charset. + if _, err := validateTableName("-leading"); err == nil { + t.Fatal("table name with leading hyphen should be rejected") + } + if _, err := validateTableName("Upper"); err == nil { + t.Fatal("uppercase table name should be rejected") + } +} + +func TestParseTableFromARNAllowsHyphens(t *testing.T) { + bucket, ns, table, err := parseTableFromARN("arn:aws:s3tables:us-east-1:123456789012:bucket/my-bucket/table/rest-integration-test/test-create-table") + require.NoError(t, err) + require.Equal(t, "my-bucket", bucket) + require.Equal(t, "rest-integration-test", ns) + require.Equal(t, "test-create-table", table) +} diff --git a/weed/s3api/s3tables/utils.go b/weed/s3api/s3tables/utils.go index 9b6aa10b6..a0bd86fb7 100644 --- a/weed/s3api/s3tables/utils.go +++ b/weed/s3api/s3tables/utils.go @@ -15,8 +15,8 @@ import ( const ( bucketNamePatternStr = `[a-z0-9-]+` - tableNamespacePatternStr = `[a-z0-9_.]+` - tableNamePatternStr = `[a-z0-9_]+` + tableNamespacePatternStr = `[a-z0-9_.-]+` + tableNamePatternStr = `[a-z0-9_-]+` ) const ( @@ -344,12 +344,12 @@ func validateNamespacePart(name string) error { return fmt.Errorf("namespace name must end with a letter or digit") } - // Allowed characters: a-z, 0-9, _ + // Allowed characters: a-z, 0-9, _, - (hyphen interior; start/end checked above) for _, ch := range name { - if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' { + if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' { continue } - return fmt.Errorf("invalid namespace name: only 'a-z', '0-9', and '_' are allowed") + return fmt.Errorf("invalid namespace name: only 'a-z', '0-9', '_', and '-' are allowed") } // Reserved prefix @@ -411,12 +411,12 @@ func validateTableName(name string) (string, error) { return "", fmt.Errorf("table name must start with a letter or digit") } - // Allowed characters: a-z, 0-9, _ + // Allowed characters: a-z, 0-9, _, - (start checked above) for _, ch := range name { - if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' { + if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' { continue } - return "", fmt.Errorf("invalid table name: only 'a-z', '0-9', and '_' are allowed") + return "", fmt.Errorf("invalid table name: only 'a-z', '0-9', '_', and '-' are allowed") } return name, nil }