You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by fr...@apache.org on 2025/11/08 23:06:44 UTC
(cassandra-sidecar) branch trunk updated: CASSSIDECAR-361: Fast Cassandra Input Validator (#273)
This is an automated email from the ASF dual-hosted git repository.
frankgh pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git
The following commit(s) were added to refs/heads/trunk by this push:
new 99320f22 CASSSIDECAR-361: Fast Cassandra Input Validator (#273)
99320f22 is described below
commit 99320f22ae58b6e81c8db5442138d95e0182c38c
Author: Francisco Guerrero <fr...@apache.org>
AuthorDate: Sat Nov 8 15:06:38 2025 -0800
CASSSIDECAR-361: Fast Cassandra Input Validator (#273)
Patch by Francisco Guerrero; reviewed by Saranya Krishnakumar for CASSSIDECAR-361
---
CHANGES.txt | 1 +
conf/sidecar.yaml | 21 +-
docs/src/user.adoc | 11 +-
examples/conf/sidecar-ccm.yaml | 4 +-
.../CassandraInputValidationConfiguration.java | 5 +
.../CassandraInputValidationConfigurationImpl.java | 30 +-
.../exceptions/CassandraInputException.java | 30 ++
.../ForbiddenCassandraInputException.java | 30 ++
.../sidecar/handlers/AbstractHandler.java | 6 +
.../cassandra/sidecar/modules/UtilitiesModule.java | 27 ++
.../sidecar/snapshots/SnapshotPathBuilder.java | 14 +-
.../sidecar/utils/CassandraInputValidator.java | 172 ++---------
.../sidecar/utils/FastCassandraInputValidator.java | 314 +++++++++++++++++++++
...java => RegexBasedCassandraInputValidator.java} | 170 +++++------
.../sidecar/config/SidecarConfigurationTest.java | 17 +-
.../sidecar/utils/CassandraInputValidatorTest.java | 154 ++++++----
.../utils/FastCassandraInputValidatorTest.java | 105 +++++++
.../RegexBasedCassandraInputValidatorTest.java | 75 +++++
.../resources/config/sidecar_file_permissions.yaml | 4 +-
.../sidecar_invalid_accesscontrol_config.yaml | 4 +-
.../config/sidecar_invalid_client_auth.yaml | 4 +-
.../config/sidecar_invalid_file_permissions.yaml | 4 +-
.../resources/config/sidecar_live_migration.yaml | 4 +-
.../src/test/resources/config/sidecar_metrics.yaml | 4 +-
.../config/sidecar_metrics_empty_filters.yaml | 4 +-
.../test/resources/config/sidecar_missing_jmx.yaml | 4 +-
.../config/sidecar_multiple_instances.yaml | 4 +-
.../config/sidecar_no_local_instances.yaml | 4 +-
.../sidecar_schema_keyspace_configuration.yaml | 4 +-
.../resources/config/sidecar_single_instance.yaml | 4 +-
.../sidecar_single_instance_non_zero_port.yaml | 4 +-
server/src/test/resources/config/sidecar_ssl.yaml | 4 +-
.../config/sidecar_unrecognized_authenticator.yaml | 4 +-
.../config/sidecar_unrecognized_authorizer.yaml | 4 +-
.../config/sidecar_validation_configuration.yaml | 9 +-
.../config/sidecar_vertx_filesystem_options.yaml | 4 +-
.../snapshots/AbstractSnapshotPathBuilderTest.java | 3 +-
37 files changed, 900 insertions(+), 366 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 90d1f9c3..5d82fef6 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
0.3.0
-----
+ * Fast Cassandra Input Validator (CASSSIDECAR-361)
* Upgrade caffeine dependency (CASSSIDECAR-332)
* Add Live Migration Status endpoint to persist Live Migration status and for safety (CASSSIDECAR-345)
* Sidecar endpoint for draining a node (CASSSIDECAR-342)
diff --git a/conf/sidecar.yaml b/conf/sidecar.yaml
index 809181ab..57be2e78 100644
--- a/conf/sidecar.yaml
+++ b/conf/sidecar.yaml
@@ -375,6 +375,23 @@ metrics:
# value: "vertx.eventbus.*" # exclude all metrics starts with vertx.eventbus
cassandra_input_validation:
+ validator:
+ # Implementation to use for the validation of Casandra inputs. Out of the box, Cassandra Sidecar provides
+ # org.apache.cassandra.sidecar.utils.{RegexBasedCassandraInputValidator, FastCassandraInputValidator}.
+ #
+ # - RegexBasedCassandraInputValidator default implementation the uses regular expressions to perform validations
+ # - FastCassandraInputValidator optimized implementation that does not use regular expressions. This implementation
+ # ignores the regular expressions configured in the cassandra_input_validation
+ # configuration.
+ - class_name: org.apache.cassandra.sidecar.utils.RegexBasedCassandraInputValidator
+ # Configuration parameters that are only applicable to the FastCassandraInputValidator implementation
+ #parameters:
+ # # Comma-separated list of terminations allowed for the component name. By default, allowed component
+ # # names can only end in .db, .cql, .json, .crc32, or TOC.txt
+ # valid_terminations: ".db,.cql,.json,.crc32,TOC.txt"
+ # # Comma-separated list of terminations allowed for the restricted component name. By default, allowed
+ # # restricted component names con only end in .db or TOC.txt
+ # valid_restricted_terminations: ".db,TOC.txt"
forbidden_keyspaces:
- system_schema
- system_traces
@@ -386,8 +403,8 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
blob_restore:
job_discovery_active_loop_delay: 5m
diff --git a/docs/src/user.adoc b/docs/src/user.adoc
index 8e78c951..d8ef90a7 100644
--- a/docs/src/user.adoc
+++ b/docs/src/user.adoc
@@ -402,11 +402,16 @@ The `metrics` configuration defines how Cassandra Sidecar exposes metrics over J
This section defines input validations for Cassandra keyspace and directory names which are used for SSTable imports. The following properties are defined:
+* `validator`: The implementation to use for the validation of Casandra inputs.
+** `class_name`: The name of the class implementing the CassandraInputValidator interface. Out of the box Cassandra Sidecar provides org.apache.cassandra.sidecar.utils.{RegexBasedCassandraInputValidator, FastCassandraInputValidator}.
+** `parameters`: Configuration parameters that are only applicable to the FastCassandraInputValidator implementation.
+*** `valid_terminations`: Comma-separated list of terminations allowed for the component name.
+*** `valid_restricted_terminations`: Comma-separated list of terminations allowed for the restricted component name.git
* `forbidden_keyspaces`: This is a list of keyspace names which are forbidden to be used for SSTable imports.
* `allowed_chars_for_directory`: This is a regular expression which defines the characters that can be used in directory names used for SSTable imports. By default `"[a-zA-Z][a-zA-Z0-9_]{0,47}"`
* `allowed_chars_for_quoted_name`: This is a regular expression which defines the characters that can be used in quoted names used for SSTable imports. If a quoted name does not match this regular expression, the SSTable import request will be rejected. By default, `"[a-zA-Z_0-9]{1,48}"`
-* `allowed_chars_for_component_name`: This is a regular expression which defines which characters can be used for SSTable component file names. By default, `"[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"`.
-* `allowed_chars_for_restricted_component_name`: This is a regular expression which defines which characters can be used in the SSTable component file names for `.db` and `TOC.txt` files. By default, `"[a-zA-Z0-9_-]+(.db|TOC.txt)"`.
+* `allowed_chars_for_component_name`: This is a regular expression which defines which characters can be used for SSTable component file names. By default, `"[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"`.
+* `allowed_chars_for_restricted_component_name`: This is a regular expression which defines which characters can be used in the SSTable component file names for `.db` and `TOC.txt` files. By default, `"[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"`.
[[blob-restore]]
### blob_restore
@@ -442,4 +447,4 @@ The `live_migration` section of the `sidecar.yaml` file is used to configure the
* `files_to_exclude`: This is a list of files to not include when migrating between machines. Both glob and regex patterns can be used.
* `dirs_to_exclude`: This is a list of directories to not include when migrating between machines. By default, `glob:${DATA_FILE_DIR}/*/*/snapshots`, to exclude snapshot directories from being migrated to the destination host.
-* `migration_map`: This is a map source to destination hostnames. For example `localhost1: localhost4` means that we will be migrating data from `localhost1` to `localhost4`.
\ No newline at end of file
+* `migration_map`: This is a map source to destination hostnames. For example `localhost1: localhost4` means that we will be migrating data from `localhost1` to `localhost4`.
diff --git a/examples/conf/sidecar-ccm.yaml b/examples/conf/sidecar-ccm.yaml
index bbd41e12..16f45096 100644
--- a/examples/conf/sidecar-ccm.yaml
+++ b/examples/conf/sidecar-ccm.yaml
@@ -433,8 +433,8 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
blob_restore:
job_discovery_active_loop_delay: 5m
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/config/CassandraInputValidationConfiguration.java b/server/src/main/java/org/apache/cassandra/sidecar/config/CassandraInputValidationConfiguration.java
index 98205a19..d77902d3 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/config/CassandraInputValidationConfiguration.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/config/CassandraInputValidationConfiguration.java
@@ -25,6 +25,11 @@ import java.util.Set;
*/
public interface CassandraInputValidationConfiguration
{
+ /**
+ * @return configuration needed to configure the {@link org.apache.cassandra.sidecar.utils.CassandraInputValidator}
+ */
+ ParameterizedClassConfiguration validatorConfiguration();
+
/**
* @return a set of forbidden keyspaces
*/
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CassandraInputValidationConfigurationImpl.java b/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CassandraInputValidationConfigurationImpl.java
index 1538fac9..db8f4218 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CassandraInputValidationConfigurationImpl.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/config/yaml/CassandraInputValidationConfigurationImpl.java
@@ -21,16 +21,22 @@ package org.apache.cassandra.sidecar.config.yaml;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import org.apache.cassandra.sidecar.config.ParameterizedClassConfiguration;
+import org.apache.cassandra.sidecar.utils.RegexBasedCassandraInputValidator;
/**
* Encapsulate configuration values for validation properties used for Cassandra inputs
*/
public class CassandraInputValidationConfigurationImpl implements CassandraInputValidationConfiguration
{
+ public static final String VALIDATOR_PROPERTY = "validator";
+ private static final ParameterizedClassConfiguration DEFAULT_VALIDATOR_CONFIGURATION
+ = new ParameterizedClassConfigurationImpl(RegexBasedCassandraInputValidator.class.getName(), Map.of());
public static final String FORBIDDEN_KEYSPACES_PROPERTY = "forbidden_keyspaces";
public static final Set<String> DEFAULT_FORBIDDEN_KEYSPACES =
Collections.unmodifiableSet(new HashSet<>(Arrays.asList("system_schema",
@@ -47,10 +53,13 @@ public class CassandraInputValidationConfigurationImpl implements CassandraInput
public static final String DEFAULT_ALLOWED_CHARS_FOR_QUOTED_NAME = "[a-zA-Z_0-9]{1,48}";
public static final String ALLOWED_CHARS_FOR_COMPONENT_NAME_PROPERTY = "allowed_chars_for_component_name";
public static final String DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME =
- "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)";
+ "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)";
public static final String ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME_PROPERTY =
"allowed_chars_for_restricted_component_name";
- public static final String DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME = "[a-zA-Z0-9_-]+(.db|TOC.txt)";
+ public static final String DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME = "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)";
+
+ @JsonProperty(value = VALIDATOR_PROPERTY)
+ protected final ParameterizedClassConfiguration validatorConfiguration;
@JsonProperty(FORBIDDEN_KEYSPACES_PROPERTY)
protected final Set<String> forbiddenKeyspaces;
@@ -69,19 +78,22 @@ public class CassandraInputValidationConfigurationImpl implements CassandraInput
public CassandraInputValidationConfigurationImpl()
{
- this(DEFAULT_FORBIDDEN_KEYSPACES,
+ this(DEFAULT_VALIDATOR_CONFIGURATION,
+ DEFAULT_FORBIDDEN_KEYSPACES,
DEFAULT_ALLOWED_CHARS_FOR_NAME,
DEFAULT_ALLOWED_CHARS_FOR_QUOTED_NAME,
DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME,
DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME);
}
- public CassandraInputValidationConfigurationImpl(Set<String> forbiddenKeyspaces,
+ public CassandraInputValidationConfigurationImpl(ParameterizedClassConfiguration validatorConfiguration,
+ Set<String> forbiddenKeyspaces,
String allowedPatternForName,
String allowedPatternForQuotedName,
String allowedPatternForComponentName,
String allowedPatternForRestrictedComponentName)
{
+ this.validatorConfiguration = validatorConfiguration;
this.forbiddenKeyspaces = forbiddenKeyspaces;
this.allowedPatternForName = allowedPatternForName;
this.allowedPatternForQuotedName = allowedPatternForQuotedName;
@@ -89,6 +101,16 @@ public class CassandraInputValidationConfigurationImpl implements CassandraInput
this.allowedPatternForRestrictedComponentName = allowedPatternForRestrictedComponentName;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @JsonProperty(value = VALIDATOR_PROPERTY)
+ public ParameterizedClassConfiguration validatorConfiguration()
+ {
+ return validatorConfiguration;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/exceptions/CassandraInputException.java b/server/src/main/java/org/apache/cassandra/sidecar/exceptions/CassandraInputException.java
new file mode 100644
index 00000000..f028d515
--- /dev/null
+++ b/server/src/main/java/org/apache/cassandra/sidecar/exceptions/CassandraInputException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.cassandra.sidecar.exceptions;
+
+/**
+ * Exception thrown when a Cassandra input is invalid
+ */
+public class CassandraInputException extends IllegalArgumentException
+{
+ public CassandraInputException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/exceptions/ForbiddenCassandraInputException.java b/server/src/main/java/org/apache/cassandra/sidecar/exceptions/ForbiddenCassandraInputException.java
new file mode 100644
index 00000000..8a671535
--- /dev/null
+++ b/server/src/main/java/org/apache/cassandra/sidecar/exceptions/ForbiddenCassandraInputException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.cassandra.sidecar.exceptions;
+
+/**
+ * Exception thrown when a Cassandra input is forbidden
+ */
+public class ForbiddenCassandraInputException extends CassandraInputException
+{
+ public ForbiddenCassandraInputException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/handlers/AbstractHandler.java b/server/src/main/java/org/apache/cassandra/sidecar/handlers/AbstractHandler.java
index a7c4f7f6..7060a3b3 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/handlers/AbstractHandler.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/handlers/AbstractHandler.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.sidecar.common.server.data.QualifiedTableName;
import org.apache.cassandra.sidecar.common.server.exceptions.JmxAuthenticationException;
import org.apache.cassandra.sidecar.common.utils.Preconditions;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.exceptions.ForbiddenCassandraInputException;
import org.apache.cassandra.sidecar.exceptions.NoSuchCassandraInstanceException;
import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
@@ -217,6 +218,11 @@ public abstract class AbstractHandler<T> implements Handler<RoutingContext>
return wrapHttpException(HttpResponseStatus.MISDIRECTED_REQUEST, cause.getMessage(), cause);
}
+ if (cause instanceof ForbiddenCassandraInputException)
+ {
+ return wrapHttpException(HttpResponseStatus.FORBIDDEN, cause.getMessage());
+ }
+
if (cause instanceof IllegalArgumentException)
{
return wrapHttpException(HttpResponseStatus.BAD_REQUEST, cause.getMessage(), cause);
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/modules/UtilitiesModule.java b/server/src/main/java/org/apache/cassandra/sidecar/modules/UtilitiesModule.java
index 858bc5ba..f7dc3430 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/modules/UtilitiesModule.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/modules/UtilitiesModule.java
@@ -25,9 +25,14 @@ import com.google.inject.name.Named;
import org.apache.cassandra.sidecar.common.server.dns.DnsResolver;
import org.apache.cassandra.sidecar.common.server.utils.DriverUtils;
import org.apache.cassandra.sidecar.common.server.utils.SidecarVersionProvider;
+import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
+import org.apache.cassandra.sidecar.exceptions.ConfigurationException;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
import org.apache.cassandra.sidecar.utils.DigestAlgorithmProvider;
+import org.apache.cassandra.sidecar.utils.FastCassandraInputValidator;
import org.apache.cassandra.sidecar.utils.JdkMd5DigestProvider;
+import org.apache.cassandra.sidecar.utils.RegexBasedCassandraInputValidator;
import org.apache.cassandra.sidecar.utils.TimeProvider;
import org.apache.cassandra.sidecar.utils.XXHash32Provider;
@@ -82,4 +87,26 @@ public class UtilitiesModule extends AbstractModule
{
return new SidecarVersionProvider("/sidecar.version");
}
+
+ @Provides
+ @Singleton
+ CassandraInputValidator cassandraInputValidatorFactory(SidecarConfiguration configuration)
+ {
+ CassandraInputValidationConfiguration config = configuration.cassandraInputValidationConfiguration();
+
+ if (config.validatorConfiguration() == null
+ || config.validatorConfiguration().className() == null
+ || RegexBasedCassandraInputValidator.class.getName().equals(config.validatorConfiguration().className()))
+ {
+ // When the validator configuration is not set assume legacy configuration
+ // and provide the regex-based validator.
+ return new RegexBasedCassandraInputValidator(config);
+ }
+ else if (FastCassandraInputValidator.class.getName().equals(config.validatorConfiguration().className()))
+ {
+ return new FastCassandraInputValidator(config);
+ }
+
+ throw new ConfigurationException("Unrecognized validator provider " + config.validatorConfiguration().className() + " configured");
+ }
}
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java b/server/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
index fd5e561a..80a9f279 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
@@ -37,7 +37,6 @@ import com.google.inject.Singleton;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import org.apache.cassandra.sidecar.cluster.InstancesMetadata;
-import org.apache.cassandra.sidecar.common.utils.Preconditions;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.handlers.data.StreamSSTableComponentRequestParam;
import org.apache.cassandra.sidecar.utils.BaseFileSystem;
@@ -57,10 +56,10 @@ public class SnapshotPathBuilder extends BaseFileSystem
* Creates a new SnapshotPathBuilder for snapshots of an instance with the given {@code vertx} instance and
* {@code instancesMetadata Cassandra configuration}.
*
- * @param vertx the vertx instance
+ * @param vertx the vertx instance
* @param instancesMetadata the configuration for Cassandra
- * @param validator a validator instance to validate Cassandra-specific input
- * @param executorPools executor pools for blocking executions
+ * @param validator a validator instance to validate Cassandra-specific input
+ * @param executorPools executor pools for blocking executions
*/
@Inject
public SnapshotPathBuilder(Vertx vertx,
@@ -158,15 +157,12 @@ public class SnapshotPathBuilder extends BaseFileSystem
validator.validateTableId(request.tableId());
}
validator.validateSnapshotName(request.snapshotName());
- // Only allow .db and TOC.txt components here
String secondaryIndexName = request.secondaryIndexName();
if (secondaryIndexName != null)
{
- Preconditions.checkArgument(!secondaryIndexName.isEmpty(), "secondaryIndexName cannot be empty");
- Preconditions.checkArgument(secondaryIndexName.charAt(0) == '.', "Invalid secondary index name");
- String indexName = secondaryIndexName.substring(1);
- validator.validatePattern(indexName, indexName, "secondary index", false);
+ validator.validateIndexName(secondaryIndexName);
}
+ // Only allow .db and TOC.txt components here
validator.validateRestrictedComponentName(request.componentName());
}
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java b/server/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
index 94e7c9f9..affdcc90 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
@@ -18,45 +18,15 @@
package org.apache.cassandra.sidecar.utils;
-import java.io.File;
-import java.util.Objects;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.vertx.ext.web.handler.HttpException;
import org.apache.cassandra.sidecar.common.server.data.Name;
-import org.apache.cassandra.sidecar.common.utils.Preconditions;
-import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
-import org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl;
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.VisibleForTesting;
/**
- * Miscellaneous methods used for validation.
+ * Interface that defines different Cassandra input validations
*/
-@Singleton
-public class CassandraInputValidator
+public interface CassandraInputValidator
{
- private final CassandraInputValidationConfiguration validationConfiguration;
-
- /**
- * Constructs a new object with the provided {@code validationConfiguration}
- *
- * @param validationConfiguration a validation configuration
- */
- @Inject
- public CassandraInputValidator(CassandraInputValidationConfiguration validationConfiguration)
- {
- this.validationConfiguration = validationConfiguration;
- }
-
- @VisibleForTesting
- public CassandraInputValidator()
- {
- this(new CassandraInputValidationConfigurationImpl());
- }
-
/**
* Validates that the {@code keyspace} is not {@code null}, that it contains valid characters, and that it's
* not a forbidden keyspace.
@@ -64,19 +34,8 @@ public class CassandraInputValidator
* @param keyspace the name of the Cassandra keyspace to validate
* @return the validated {@code keyspace}
* @throws NullPointerException when the {@code keyspace} is {@code null}
- * @throws HttpException when the {@code keyspace} contains invalid characters in the name or when the
- * keyspace is forbidden
*/
- public Name validateKeyspaceName(@NotNull String keyspace)
- {
- Name name = new Name(keyspace);
- validateNamePattern(name, "keyspace");
-
- if (validationConfiguration.forbiddenKeyspaces().contains(name.name()))
- throw new HttpException(HttpResponseStatus.FORBIDDEN.code(), "Forbidden keyspace: " + keyspace);
-
- return name;
- }
+ Name validateKeyspaceName(@NotNull String keyspace) throws NullPointerException;
/**
* Validates that the {@code tableName} is not {@code null}, and it contains allowed character for Cassandra
@@ -84,15 +43,10 @@ public class CassandraInputValidator
*
* @param tableName the name of the Cassandra table to validate
* @return the validated {@code tableName}
- * @throws NullPointerException when the {@code tableName} is {@code null}
- * @throws HttpException when the {@code tableName} contains invalid characters in the name
+ * @throws NullPointerException when the {@code tableName} is {@code null}
+ * @throws CassandraInputException when the {@code tableName} contains invalid characters in the name
*/
- public Name validateTableName(@NotNull String tableName)
- {
- Name name = new Name(tableName);
- validateNamePattern(name, "table name");
- return name;
- }
+ Name validateTableName(@NotNull String tableName);
/**
* Validates that the {@code snapshotName} is not {@code null}, and it contains allowed character for the
@@ -100,18 +54,10 @@ public class CassandraInputValidator
*
* @param snapshotName the name of the Cassandra snapshot to validate
* @return the validated {@code snapshotName}
- * @throws NullPointerException when the {@code snapshotName} is {@code null}
- * @throws HttpException when the {@code snapshotName} contains invalid characters in the name
+ * @throws NullPointerException when the {@code snapshotName} is {@code null}
+ * @throws CassandraInputException when the {@code snapshotName} contains invalid characters in the name
*/
- public String validateSnapshotName(@NotNull String snapshotName)
- {
- Objects.requireNonNull(snapshotName, "snapshotName must not be null");
- // most UNIX systems only disallow file separator and null characters for directory names
- if (snapshotName.contains(File.separator) || snapshotName.contains("\0"))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid characters in snapshot name: " + snapshotName);
- return snapshotName;
- }
+ String validateSnapshotName(@NotNull String snapshotName);
/**
* Validates that the {@code componentName} is not {@code null}, and it contains allowed names for the
@@ -119,14 +65,10 @@ public class CassandraInputValidator
*
* @param componentName the name of the SSTable component to validate
* @return the validated {@code componentName}
- * @throws NullPointerException when the {@code componentName} is null
- * @throws HttpException when the {@code componentName} is not valid
+ * @throws NullPointerException when the {@code componentName} is null
+ * @throws CassandraInputException when the {@code componentName} is not valid
*/
- public String validateComponentName(@NotNull String componentName)
- {
- return validateComponentNameByRegex(componentName,
- validationConfiguration.allowedPatternForComponentName());
- }
+ String validateComponentName(@NotNull String componentName);
/**
* Validates that the {@code componentName} is not {@code null}, and it contains a subset of allowed names for the
@@ -134,94 +76,32 @@ public class CassandraInputValidator
*
* @param componentName the name of the SSTable component to validate
* @return the validated {@code componentName}
- * @throws NullPointerException when the {@code componentName} is null
- * @throws HttpException when the {@code componentName} is not a valid name for the configured restricted
- * component name
+ * @throws NullPointerException when the {@code componentName} is null
+ * @throws CassandraInputException when the {@code componentName} is not a valid name for the configured restricted
+ * component name
*/
- public String validateRestrictedComponentName(@NotNull String componentName)
- {
- return validateComponentNameByRegex(componentName,
- validationConfiguration.allowedPatternForRestrictedComponentName());
- }
+ String validateRestrictedComponentName(@NotNull String componentName);
/**
- * Validates the {@code componentName} against the provided {@code regex}.
+ * Validates that the index name has only valid characters
*
- * @param componentName the name of the SSTable component
- * @param regex the regex for validation
- * @return the validated {@code componentName}
- * @throws NullPointerException when the {@code componentName} is null
- * @throws HttpException when the {@code componentName} does not match the provided regex
+ * @param secondaryIndexName the name of the secondary index
*/
- @NotNull
- private String validateComponentNameByRegex(String componentName, String regex)
- {
- Objects.requireNonNull(componentName, "componentName must not be null");
- if (!componentName.matches(regex))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid component name: " + componentName);
- return componentName;
- }
-
- /**
- * Validates that the {@code unquotedInput} matches the {@code patternWordChars}
- *
- * @param unquotedInput the unquoted input
- * @param maybeQuoted the original input used for the exception message
- * @param exceptionHint hint to add in the exception message
- * @param isQuotedFromSource whether the name was quoted from source
- * @throws HttpException when the {@code unquotedInput} does not match the pattern
- */
- public void validatePattern(String unquotedInput, String maybeQuoted,
- String exceptionHint, boolean isQuotedFromSource)
- {
- String pattern = isQuotedFromSource
- ? validationConfiguration.allowedPatternForQuotedName()
- : validationConfiguration.allowedPatternForName();
-
- if (!unquotedInput.matches(pattern))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid characters in " + exceptionHint + ": " + maybeQuoted);
- }
+ void validateIndexName(String secondaryIndexName);
/**
* Validates that the unique table identifier is a valid hexadecimal
*
* @param tableId the table identifier to validate
*/
- public void validateTableId(String tableId)
- {
- Objects.requireNonNull(tableId, "tableId must not be null");
- Preconditions.checkArgument(tableId.length() <= 32, "tableId cannot be longer than 32 characters");
- for (int i = 0; i < tableId.length(); i++)
- {
- char c = tableId.charAt(i);
- if (!isHex(c))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid characters in table id: " + tableId);
- }
- }
-
- /**
- * @param c the character to test
- * @return {@code true} if the input {@code c} is valid hexadecimal, {@code false} otherwise
- */
- protected boolean isHex(char c)
- {
- return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F');
- }
+ void validateTableId(String tableId);
/**
- * Removes the surrounding quotes for the name, if the quotes are present. Otherwise, returns the original
- * input.
- * Validates that the {@code name} matches the {@code patternWordChars}
+ * Validates that the {@code name} matches the name pattern
*
- * @param name name
- * @param exceptionHint hint to add in the exception message
- * @throws HttpException when the {@code unquotedInput} does not match the pattern
+ * @param name name
+ * @param exceptionHint hint to add in the exception message
+ * @throws CassandraInputException when the {@code unquotedInput} does not match the pattern
*/
- public void validateNamePattern(Name name, String exceptionHint)
- {
- validatePattern(name.name(), name.maybeQuotedName(), exceptionHint, name.isSourceQuoted());
- }
+ void validateNamePattern(Name name, String exceptionHint);
}
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/utils/FastCassandraInputValidator.java b/server/src/main/java/org/apache/cassandra/sidecar/utils/FastCassandraInputValidator.java
new file mode 100644
index 00000000..99a0f70b
--- /dev/null
+++ b/server/src/main/java/org/apache/cassandra/sidecar/utils/FastCassandraInputValidator.java
@@ -0,0 +1,314 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.cassandra.sidecar.common.server.data.Name;
+import org.apache.cassandra.sidecar.common.utils.Preconditions;
+import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl;
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
+import org.apache.cassandra.sidecar.exceptions.ForbiddenCassandraInputException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.VisibleForTesting;
+
+/**
+ * An implementation of the {@link CassandraInputValidator} that does not use regular expressions
+ * for validations and uses optimized validations.
+ */
+public class FastCassandraInputValidator extends RegexBasedCassandraInputValidator
+{
+ /**
+ * Longest permissible keyspace name
+ */
+ public static final int KEYSPACE_NAME_LENGTH = 48;
+ /**
+ * Longest permissible table name. See CASSANDRA-20389 (Create table fails on long table names) for
+ * details about the maximum length for table names.
+ */
+ public static final int TABLE_NAME_LENGTH = 222;
+ /**
+ * Default valid component name terminations
+ */
+ public static final List<String> DEFAULT_VALID_TERMINATIONS = List.of(".db", ".cql", ".json", ".crc32", "TOC.txt");
+
+ /**
+ * Default valid component name terminations for restricted component names
+ */
+ public static final List<String> DEFAULT_VALID_RESTRICTED_TERMINATIONS = List.of(".db", "TOC.txt");
+
+ @VisibleForTesting
+ final List<String> validTerminations;
+ @VisibleForTesting
+ final List<String> validRestrictedTerminations;
+
+ @VisibleForTesting
+ public FastCassandraInputValidator()
+ {
+ this(new CassandraInputValidationConfigurationImpl());
+ }
+
+ /**
+ * Constructs a new object with the provided {@code validationConfiguration}
+ *
+ * @param validationConfiguration a validation configuration
+ */
+ public FastCassandraInputValidator(CassandraInputValidationConfiguration validationConfiguration)
+ {
+ super(validationConfiguration);
+ Map<String, String> configMap = validationConfiguration.validatorConfiguration().namedParameters();
+ validTerminations = parseConfiguredOrDefault(configMap, "valid_terminations", DEFAULT_VALID_TERMINATIONS);
+ validRestrictedTerminations = parseConfiguredOrDefault(configMap, "valid_restricted_terminations", DEFAULT_VALID_RESTRICTED_TERMINATIONS);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Name validateKeyspaceName(@NotNull String keyspace) throws NullPointerException
+ {
+ Name name = new Name(keyspace);
+ validateNameLength(name, "keyspace", KEYSPACE_NAME_LENGTH);
+ validateNamePattern(name, "keyspace");
+
+ if (validationConfiguration.forbiddenKeyspaces().contains(name.name()))
+ throw new ForbiddenCassandraInputException("Forbidden keyspace: " + keyspace);
+
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Name validateTableName(@NotNull String tableName)
+ {
+ Name name = new Name(tableName);
+ validateNameLength(name, "table name", TABLE_NAME_LENGTH);
+ validateNamePattern(name, "table name");
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String validateComponentName(@NotNull String componentName)
+ {
+ Objects.requireNonNull(componentName, "componentName must not be null");
+ Preconditions.checkArgument(!componentName.isEmpty(), () -> "componentName cannot be empty");
+ validateComponentName(componentName, validTerminations);
+ return componentName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String validateRestrictedComponentName(@NotNull String componentName)
+ {
+ Objects.requireNonNull(componentName, "componentName must not be null");
+ Preconditions.checkArgument(!componentName.isEmpty(), () -> "componentName cannot be empty");
+ validateComponentName(componentName, validRestrictedTerminations);
+ return componentName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void validateIndexName(String secondaryIndexName)
+ {
+ Preconditions.checkArgument(!secondaryIndexName.isEmpty(), "secondaryIndexName cannot be empty");
+ if (secondaryIndexName.charAt(0) != '.')
+ throw new CassandraInputException("Invalid secondary index name: " + secondaryIndexName);
+ validateNamePattern(secondaryIndexName, secondaryIndexName, false, "secondary index", 1 /* skip the first character */);
+ }
+
+ /**
+ * Validates that the {@code name} is a valid name in Cassandra as defined by the grammar in
+ * <a href="https://cassandra.apache.org/doc/4.1/cassandra/cql/ddl.html#common-definitions">Cassandra CQL common
+ * definitions</a>
+ *
+ * @param name name to validate
+ * @param exceptionHint hint to add in the exception message
+ * @throws CassandraInputException when the {@code unquotedInput} has invalid characters
+ */
+ @Override
+ public void validateNamePattern(Name name, String exceptionHint)
+ {
+ validateNamePattern(name.name(), name.maybeQuotedName(), name.isSourceQuoted(), exceptionHint, 0);
+ }
+
+ /**
+ * Validates that the {@code name} is a valid name in Cassandra as defined by the grammar in
+ * <a href="https://cassandra.apache.org/doc/4.1/cassandra/cql/ddl.html#common-definitions">Cassandra CQL common
+ * definitions</a>. The validation will only take into account the start index.
+ *
+ * @param unquotedName the unquoted name to validate
+ * @param maybeQuotedName the name that maybe quoted
+ * @param isSourceQuoted whether the source will be quoted
+ * @param exceptionHint hint to add in the exception message
+ * @param startIndex start index
+ * @throws CassandraInputException when the {@code unquotedName} has invalid characters
+ */
+ protected void validateNamePattern(String unquotedName, String maybeQuotedName, boolean isSourceQuoted,
+ String exceptionHint, int startIndex)
+ {
+ char c;
+ if (!isSourceQuoted)
+ {
+ // Validate the first character. Unquoted names can only begin with a letter
+ c = unquotedName.charAt(startIndex++);
+ if (!isLetter(c))
+ throw new CassandraInputException("Invalid characters in " + exceptionHint + ": " + maybeQuotedName);
+ }
+
+ while (startIndex < unquotedName.length())
+ {
+ c = unquotedName.charAt(startIndex++);
+ if (!isAlphanumeric(c) && !isUnderscore(c))
+ throw new CassandraInputException("Invalid characters in " + exceptionHint + ": " + maybeQuotedName);
+ }
+ }
+
+ /**
+ * Validates that the {@code name} has valid length
+ *
+ * @param name name to validate
+ * @param exceptionHint hint to add in the exception message
+ * @param maxNameLength the maximum length for the name
+ * @throws CassandraInputException when the length of the {@code unquotedInput} is empty or larger than
+ * {@code maxNameLength}
+ */
+ protected void validateNameLength(Name name, String exceptionHint, int maxNameLength)
+ {
+ String unquotedInput = name.name();
+ if (unquotedInput.isEmpty() || unquotedInput.length() > maxNameLength)
+ throw new CassandraInputException("Invalid length " + unquotedInput.length() +
+ " for " + exceptionHint + ": " + name.maybeQuotedName());
+ }
+
+ /**
+ * Validates that the {@code componentName} has valid characters, and it ends with one of the
+ * {@code validTerminations}.
+ *
+ * @param componentName the name of the component to validate
+ * @param validTerminations a list of valid terminations for the component name
+ */
+ protected void validateComponentName(String componentName, List<String> validTerminations)
+ {
+ char c;
+ int lastIndexOfAllowedTermination = lastIndexOfAllowedTermination(componentName, validTerminations);
+
+ if (lastIndexOfAllowedTermination < 1)
+ throw new CassandraInputException("Invalid component name: " + componentName);
+
+ for (int i = lastIndexOfAllowedTermination - 1; i >= 0; i--)
+ {
+ c = componentName.charAt(i);
+ if (!isValidComponentNameCharacter(c))
+ throw new CassandraInputException("Invalid component name: " + componentName);
+ }
+ }
+
+ /**
+ * @param componentName the name of the component to validate
+ * @param validTerminations a list of valid terminations for the component name
+ * @return the last index of the first found allowed termination from the provided list of
+ * {@code validTerminations}, or {@code -1} if no index is found in any of the {@code validTerminations}
+ */
+ protected int lastIndexOfAllowedTermination(String componentName, List<String> validTerminations)
+ {
+ for (String allowedExtension : validTerminations)
+ {
+ if (componentName.endsWith(allowedExtension))
+ return componentName.length() - allowedExtension.length();
+ }
+ return -1;
+ }
+
+ /**
+ * @param c the character to test
+ * @return {@code true} if the input {@code c} is a valid alphanumeric character, underscore, or a dash;
+ * {@code false} otherwise
+ */
+ protected boolean isValidComponentNameCharacter(char c)
+ {
+ return isAlphanumeric(c) || isUnderscore(c) || isDash(c);
+ }
+
+ /**
+ * @param c the character to test
+ * @return {@code true} if the input {@code c} is a valid alphanumeric character, {@code false} otherwise
+ */
+ protected boolean isAlphanumeric(char c)
+ {
+ return (c >= '0' && c <= '9') || isLetter(c);
+ }
+
+ /**
+ * @param c the character to test
+ * @return {@code true} if the input {@code c} is a valid letter, {@code false} otherwise
+ */
+ protected boolean isLetter(char c)
+ {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+
+ /**
+ * @param c the character to test
+ * @return {@code true} if the input {@code c} is an underscore, {@code false} otherwise
+ */
+ protected boolean isUnderscore(char c)
+ {
+ return c == '_';
+ }
+
+ /**
+ * @param c the character to test
+ * @return {@code true} if the input {@code c} is an underscore, {@code false} otherwise
+ */
+ protected boolean isDash(char c)
+ {
+ return c == '-';
+ }
+
+ /**
+ * @param configMap the configuration map
+ * @param key the key in the map
+ * @param defaultValue the default values to provide when the value is not available for parsing
+ * @return the parsed value, or the default value when unavailable
+ */
+ static List<String> parseConfiguredOrDefault(Map<String, String> configMap, String key, List<String> defaultValue)
+ {
+ if (configMap != null)
+ {
+ String value = configMap.get(key);
+ if (value != null && !value.isEmpty())
+ {
+ return List.of(value.split(","));
+ }
+ }
+ return defaultValue;
+ }
+}
diff --git a/server/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java b/server/src/main/java/org/apache/cassandra/sidecar/utils/RegexBasedCassandraInputValidator.java
similarity index 50%
copy from server/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
copy to server/src/main/java/org/apache/cassandra/sidecar/utils/RegexBasedCassandraInputValidator.java
index 94e7c9f9..4de8bf5a 100644
--- a/server/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
+++ b/server/src/main/java/org/apache/cassandra/sidecar/utils/RegexBasedCassandraInputValidator.java
@@ -21,72 +21,58 @@ package org.apache.cassandra.sidecar.utils;
import java.io.File;
import java.util.Objects;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.vertx.ext.web.handler.HttpException;
import org.apache.cassandra.sidecar.common.server.data.Name;
import org.apache.cassandra.sidecar.common.utils.Preconditions;
import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
import org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl;
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
+import org.apache.cassandra.sidecar.exceptions.ForbiddenCassandraInputException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;
/**
* Miscellaneous methods used for validation.
*/
-@Singleton
-public class CassandraInputValidator
+public class RegexBasedCassandraInputValidator implements CassandraInputValidator
{
- private final CassandraInputValidationConfiguration validationConfiguration;
+ protected final CassandraInputValidationConfiguration validationConfiguration;
+
+ @VisibleForTesting
+ RegexBasedCassandraInputValidator()
+ {
+ this(new CassandraInputValidationConfigurationImpl());
+ }
/**
* Constructs a new object with the provided {@code validationConfiguration}
*
* @param validationConfiguration a validation configuration
*/
- @Inject
- public CassandraInputValidator(CassandraInputValidationConfiguration validationConfiguration)
+ public RegexBasedCassandraInputValidator(CassandraInputValidationConfiguration validationConfiguration)
{
this.validationConfiguration = validationConfiguration;
}
- @VisibleForTesting
- public CassandraInputValidator()
- {
- this(new CassandraInputValidationConfigurationImpl());
- }
-
/**
- * Validates that the {@code keyspace} is not {@code null}, that it contains valid characters, and that it's
- * not a forbidden keyspace.
- *
- * @param keyspace the name of the Cassandra keyspace to validate
- * @return the validated {@code keyspace}
- * @throws NullPointerException when the {@code keyspace} is {@code null}
- * @throws HttpException when the {@code keyspace} contains invalid characters in the name or when the
- * keyspace is forbidden
+ * {@inheritDoc}
*/
+ @Override
public Name validateKeyspaceName(@NotNull String keyspace)
{
Name name = new Name(keyspace);
- validateNamePattern(name, "keyspace");
if (validationConfiguration.forbiddenKeyspaces().contains(name.name()))
- throw new HttpException(HttpResponseStatus.FORBIDDEN.code(), "Forbidden keyspace: " + keyspace);
+ throw new ForbiddenCassandraInputException("Forbidden keyspace: " + keyspace);
+
+ validateNamePattern(name, "keyspace");
return name;
}
/**
- * Validates that the {@code tableName} is not {@code null}, and it contains allowed character for Cassandra
- * table names.
- *
- * @param tableName the name of the Cassandra table to validate
- * @return the validated {@code tableName}
- * @throws NullPointerException when the {@code tableName} is {@code null}
- * @throws HttpException when the {@code tableName} contains invalid characters in the name
+ * {@inheritDoc}
*/
+ @Override
public Name validateTableName(@NotNull String tableName)
{
Name name = new Name(tableName);
@@ -95,33 +81,22 @@ public class CassandraInputValidator
}
/**
- * Validates that the {@code snapshotName} is not {@code null}, and it contains allowed character for the
- * Cassandra snapshot names.
- *
- * @param snapshotName the name of the Cassandra snapshot to validate
- * @return the validated {@code snapshotName}
- * @throws NullPointerException when the {@code snapshotName} is {@code null}
- * @throws HttpException when the {@code snapshotName} contains invalid characters in the name
+ * {@inheritDoc}
*/
+ @Override
public String validateSnapshotName(@NotNull String snapshotName)
{
Objects.requireNonNull(snapshotName, "snapshotName must not be null");
// most UNIX systems only disallow file separator and null characters for directory names
if (snapshotName.contains(File.separator) || snapshotName.contains("\0"))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid characters in snapshot name: " + snapshotName);
+ throw new CassandraInputException("Invalid characters in snapshot name: " + snapshotName);
return snapshotName;
}
/**
- * Validates that the {@code componentName} is not {@code null}, and it contains allowed names for the
- * Cassandra SSTable component.
- *
- * @param componentName the name of the SSTable component to validate
- * @return the validated {@code componentName}
- * @throws NullPointerException when the {@code componentName} is null
- * @throws HttpException when the {@code componentName} is not valid
+ * {@inheritDoc}
*/
+ @Override
public String validateComponentName(@NotNull String componentName)
{
return validateComponentNameByRegex(componentName,
@@ -129,15 +104,9 @@ public class CassandraInputValidator
}
/**
- * Validates that the {@code componentName} is not {@code null}, and it contains a subset of allowed names for the
- * Cassandra SSTable component.
- *
- * @param componentName the name of the SSTable component to validate
- * @return the validated {@code componentName}
- * @throws NullPointerException when the {@code componentName} is null
- * @throws HttpException when the {@code componentName} is not a valid name for the configured restricted
- * component name
+ * {@inheritDoc}
*/
+ @Override
public String validateRestrictedComponentName(@NotNull String componentName)
{
return validateComponentNameByRegex(componentName,
@@ -145,50 +114,33 @@ public class CassandraInputValidator
}
/**
- * Validates the {@code componentName} against the provided {@code regex}.
- *
- * @param componentName the name of the SSTable component
- * @param regex the regex for validation
- * @return the validated {@code componentName}
- * @throws NullPointerException when the {@code componentName} is null
- * @throws HttpException when the {@code componentName} does not match the provided regex
+ * {@inheritDoc}
*/
- @NotNull
- private String validateComponentNameByRegex(String componentName, String regex)
+ @Override
+ public void validateIndexName(String secondaryIndexName)
{
- Objects.requireNonNull(componentName, "componentName must not be null");
- if (!componentName.matches(regex))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid component name: " + componentName);
- return componentName;
+ Preconditions.checkArgument(!secondaryIndexName.isEmpty(), "secondaryIndexName cannot be empty");
+ Preconditions.checkArgument(secondaryIndexName.charAt(0) == '.', "Invalid secondary index name");
+ String indexName = secondaryIndexName.substring(1);
+ validatePattern(indexName, indexName, "secondary index", false);
}
/**
- * Validates that the {@code unquotedInput} matches the {@code patternWordChars}
- *
- * @param unquotedInput the unquoted input
- * @param maybeQuoted the original input used for the exception message
- * @param exceptionHint hint to add in the exception message
- * @param isQuotedFromSource whether the name was quoted from source
- * @throws HttpException when the {@code unquotedInput} does not match the pattern
+ * {@inheritDoc}
*/
- public void validatePattern(String unquotedInput, String maybeQuoted,
- String exceptionHint, boolean isQuotedFromSource)
+ @NotNull
+ private String validateComponentNameByRegex(String componentName, String regex)
{
- String pattern = isQuotedFromSource
- ? validationConfiguration.allowedPatternForQuotedName()
- : validationConfiguration.allowedPatternForName();
-
- if (!unquotedInput.matches(pattern))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid characters in " + exceptionHint + ": " + maybeQuoted);
+ Objects.requireNonNull(componentName, "componentName must not be null");
+ if (!componentName.matches(regex))
+ throw new CassandraInputException("Invalid component name: " + componentName);
+ return componentName;
}
/**
- * Validates that the unique table identifier is a valid hexadecimal
- *
- * @param tableId the table identifier to validate
+ * {@inheritDoc}
*/
+ @Override
public void validateTableId(String tableId)
{
Objects.requireNonNull(tableId, "tableId must not be null");
@@ -197,31 +149,45 @@ public class CassandraInputValidator
{
char c = tableId.charAt(i);
if (!isHex(c))
- throw new HttpException(HttpResponseStatus.BAD_REQUEST.code(),
- "Invalid characters in table id: " + tableId);
+ throw new CassandraInputException("Invalid characters in table id: " + tableId);
}
}
/**
- * @param c the character to test
- * @return {@code true} if the input {@code c} is valid hexadecimal, {@code false} otherwise
+ * {@inheritDoc}
*/
- protected boolean isHex(char c)
+ @Override
+ public void validateNamePattern(Name name, String exceptionHint)
{
- return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F');
+ validatePattern(name.name(), name.maybeQuotedName(), exceptionHint, name.isSourceQuoted());
}
/**
- * Removes the surrounding quotes for the name, if the quotes are present. Otherwise, returns the original
- * input.
- * Validates that the {@code name} matches the {@code patternWordChars}
+ * Validates that the {@code unquotedInput} matches the {@code patternWordChars}
*
- * @param name name
+ * @param unquotedInput the unquoted input
+ * @param maybeQuoted the original input used for the exception message
* @param exceptionHint hint to add in the exception message
- * @throws HttpException when the {@code unquotedInput} does not match the pattern
+ * @param isQuotedFromSource whether the name was quoted from source
+ * @throws CassandraInputException when the {@code unquotedInput} does not match the pattern
*/
- public void validateNamePattern(Name name, String exceptionHint)
+ protected void validatePattern(String unquotedInput, String maybeQuoted,
+ String exceptionHint, boolean isQuotedFromSource)
{
- validatePattern(name.name(), name.maybeQuotedName(), exceptionHint, name.isSourceQuoted());
+ String pattern = isQuotedFromSource
+ ? validationConfiguration.allowedPatternForQuotedName()
+ : validationConfiguration.allowedPatternForName();
+
+ if (!unquotedInput.matches(pattern))
+ throw new CassandraInputException("Invalid characters in " + exceptionHint + ": " + maybeQuoted);
+ }
+
+ /**
+ * @param c the character to test
+ * @return {@code true} if the input {@code c} is valid hexadecimal, {@code false} otherwise
+ */
+ protected boolean isHex(char c)
+ {
+ return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F');
}
}
diff --git a/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java b/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
index 45e763b9..db83cedd 100644
--- a/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
+++ b/server/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
@@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
@@ -98,13 +99,19 @@ class SidecarConfigurationTest
CassandraInputValidationConfiguration validationConfiguration =
configuration.cassandraInputValidationConfiguration();
+ assertThat(validationConfiguration.validatorConfiguration()).isNotNull();
+ assertThat(validationConfiguration.validatorConfiguration().className())
+ .isEqualTo("org.apache.cassandra.sidecar.utils.FastCassandraInputValidator");
+ assertThat(validationConfiguration.validatorConfiguration().namedParameters())
+ .containsExactlyInAnyOrderEntriesOf(Map.of("valid_terminations", ".abc,.def",
+ "valid_restricted_terminations", ".xml"));
assertThat(validationConfiguration.forbiddenKeyspaces()).contains("a", "b", "c");
assertThat(validationConfiguration.allowedPatternForName()).isEqualTo("[a-z]+");
assertThat(validationConfiguration.allowedPatternForQuotedName()).isEqualTo("[A-Z]+");
assertThat(validationConfiguration.allowedPatternForComponentName())
- .isEqualTo("(.db|.cql|.json|.crc32|TOC.txt)");
+ .isEqualTo("(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)");
assertThat(validationConfiguration.allowedPatternForRestrictedComponentName())
- .isEqualTo("(.db|TOC.txt)");
+ .isEqualTo("(\\.db|TOC\\.txt)");
}
@Test
@@ -492,7 +499,7 @@ class SidecarConfigurationTest
void testDnsResolverResolveToIp() throws Exception
{
String yaml = "sidecar:\n" +
- " dns_resolver: resolve_to_ip";
+ " dns_resolver: resolve_to_ip";
SidecarConfiguration config = SidecarConfigurationImpl.fromYamlString(yaml);
ServiceConfiguration serviceConfiguration = config.serviceConfiguration();
assertThat(serviceConfiguration).isNotNull();
@@ -740,8 +747,8 @@ class SidecarConfigurationTest
assertThat(config.allowedPatternForName()).isEqualTo("[a-zA-Z][a-zA-Z0-9_]{0,47}");
assertThat(config.allowedPatternForQuotedName()).isEqualTo("[a-zA-Z_0-9]{1,48}");
assertThat(config.allowedPatternForComponentName())
- .isEqualTo("[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)");
- assertThat(config.allowedPatternForRestrictedComponentName()).isEqualTo("[a-zA-Z0-9_-]+(.db|TOC.txt)");
+ .isEqualTo("[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)");
+ assertThat(config.allowedPatternForRestrictedComponentName()).isEqualTo("[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)");
}
void validateVertxFilesystemOptionsClasspathResolvingDisabled(FileSystemOptionsConfiguration config)
diff --git a/server/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java b/server/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java
index 4a98dabb..400c77d1 100644
--- a/server/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java
+++ b/server/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java
@@ -24,48 +24,50 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.vertx.ext.web.handler.HttpException;
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
/**
* Test validation methods.
*/
-public class CassandraInputValidatorTest
+abstract class CassandraInputValidatorTest
{
CassandraInputValidator instance;
@BeforeEach
void setup()
{
- instance = new CassandraInputValidator();
+ instance = initializeInstanceForTest();
}
+ protected abstract CassandraInputValidator initializeInstanceForTest();
+
@ParameterizedTest(name = "[{0}]")
@ValueSource(strings = { "test_table_name", "\"test_table_name\"", "testTableName", "\"testTableName\"", "a_",
"\"cycling\"", "\"Helmets\"", "\"mIxEd_cAsE\"", "a8", "a", "\"8a\"",
"\"_must_begin_with_alphabetic_unless_quoted_p\"" })
- public void testValidTableNameValidation(String tableName)
+ void testValidTableNameValidation(String tableName)
{
instance.validateTableName(tableName);
}
@ParameterizedTest(name = "[{0}]")
- @ValueSource(strings = { "", "test table", "_must_begin_with_alphabetic", "dash-is-not-allowed", "\"\"", "\"",
+ @ValueSource(strings = { "test table", "_must_begin_with_alphabetic", "dash-is-not-allowed", "\"",
"\"inv@lid_chars\"", "test:table_name", "test-table$name", "8a", "testTable/Name" })
- public void failsWithInvalidTableName(String tableName)
+ void failsWithInvalidTableName(String tableName)
{
- HttpException httpEx = Assertions.assertThrows(HttpException.class,
- () -> instance.validateTableName(tableName));
- assertThat(httpEx.getStatusCode()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code());
- assertThat(httpEx.getPayload()).isEqualTo("Invalid characters in table name: " + tableName);
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateTableName(tableName))
+ .withMessage("Invalid characters in table name: " + tableName);
}
@ParameterizedTest(name = "[{0}]")
@ValueSource(strings = { "SystemViews", "system_views_test", "\"keyspace\"", "\"cycling\"", "\"Cycling\"",
- "\"mIxEd_cAsE\"", "a8", "a", "a_", "\"_a\"" })
- public void testValidKeyspaceValidation(String keyspace)
+ "\"mIxEd_cAsE\"", "a8", "a", "a_", "\"_a\"", "keyspace_name_can_only_have_48_characters_345678" })
+ void testValidKeyspaceValidation(String keyspace)
{
instance.validateKeyspaceName(keyspace);
}
@@ -78,70 +80,60 @@ public class CassandraInputValidatorTest
"system_auth",
"system_views",
"system_virtual_schema" })
- public void failsWithForbiddenKeyspace(String keyspace)
+ void failsWithForbiddenKeyspace(String keyspace)
{
- HttpException httpEx = Assertions.assertThrows(HttpException.class,
- () -> instance.validateKeyspaceName(keyspace));
- assertThat(httpEx.getStatusCode()).isEqualTo(HttpResponseStatus.FORBIDDEN.code());
- assertThat(httpEx.getPayload()).isEqualTo("Forbidden keyspace: " + keyspace);
+ assertThatExceptionOfType(CassandraInputException.class).isThrownBy(() -> instance.validateKeyspaceName(keyspace))
+ .withMessage("Forbidden keyspace: " + keyspace);
}
@ParameterizedTest(name = "[{0}]")
- @ValueSource(strings = { "", "test keyspace", "_cycling", "dash-is-not-allowed", "\"\"", "\"",
- "\"inv@lid_chars\"", "8a" })
- public void failsWithInvalidKeyspaceName(String keyspace)
+ @ValueSource(strings = { "test keyspace", "_cycling", "dash-is-not-allowed", "\"", "\"inv@lid_chars\"", "8a" })
+ void failsWithInvalidKeyspaceName(String keyspace)
{
- HttpException httpEx = Assertions.assertThrows(HttpException.class,
- () -> instance.validateKeyspaceName(keyspace));
- assertThat(httpEx.getStatusCode()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code());
- assertThat(httpEx.getPayload()).isEqualTo("Invalid characters in keyspace: " + keyspace);
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateKeyspaceName(keyspace))
+ .withMessage("Invalid characters in keyspace: " + keyspace);
}
- @Test
- public void testValidateFileName_validFileNames_expectNoException()
+ @ParameterizedTest(name = "[{0}]")
+ @ValueSource(strings = { "test-file-name.db", "test_file_name.json", "testFileName.cql", "t_TOC.txt", "crcfile.crc32" })
+ void testValidateFileName_validFileNames_expectNoException(String componentName)
{
- instance.validateComponentName("test-file-name.db");
- instance.validateComponentName("test_file_name.json");
- instance.validateComponentName("testFileName.cql");
- instance.validateComponentName("t_TOC.txt");
- instance.validateComponentName("crcfile.crc32");
+ instance.validateComponentName(componentName);
}
private void testCommon_testInvalidFileName(String testFileName)
{
- HttpException httpEx = Assertions.assertThrows(HttpException.class,
- () -> instance.validateComponentName(testFileName));
- assertThat(httpEx.getStatusCode()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code());
- assertThat(httpEx.getPayload()).isEqualTo("Invalid component name: " + testFileName);
+ assertThatExceptionOfType(CassandraInputException.class).isThrownBy(() -> instance.validateComponentName(testFileName))
+ .withMessage("Invalid component name: " + testFileName);
}
@Test
- public void testValidateFileName_withoutExtension_expectException()
+ void testValidateFileName_withoutExtension_expectException()
{
testCommon_testInvalidFileName("test-file-name");
}
@Test
- public void testValidateFileName_incorrectExtension_expectException()
+ void testValidateFileName_incorrectExtension_expectException()
{
testCommon_testInvalidFileName("test-file-name.db1");
}
@Test
- public void testValidateFileName_incorrectCrcExtension_expectException()
+ void testValidateFileName_incorrectCrcExtension_expectException()
{
testCommon_testInvalidFileName("crcfile.crc64");
}
@Test
- public void testValidateFileName_withoutFileName_expectException()
+ void testValidateFileName_withoutFileName_expectException()
{
testCommon_testInvalidFileName("TOC.txt");
}
-
@Test
- public void testValidateSnapshotName_validSnapshotNames_expectNoException()
+ void testValidateSnapshotName_validSnapshotNames_expectNoException()
{
instance.validateSnapshotName("valid-snapshot-name");
instance.validateSnapshotName("valid\\snapshot\\name");
@@ -151,23 +143,19 @@ public class CassandraInputValidatorTest
}
@Test
- public void testValidateSnapshotName_snapshotNameWithSlash_expectException()
+ void testValidateSnapshotName_snapshotNameWithSlash_expectException()
{
String testSnapName = "valid" + '/' + "snapshotname";
- HttpException httpEx = Assertions.assertThrows(HttpException.class,
- () -> instance.validateSnapshotName(testSnapName));
- assertThat(httpEx.getStatusCode()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code());
- assertThat(httpEx.getPayload()).isEqualTo("Invalid characters in snapshot name: " + testSnapName);
+ assertThatExceptionOfType(CassandraInputException.class).isThrownBy(() -> instance.validateSnapshotName(testSnapName))
+ .withMessage("Invalid characters in snapshot name: " + testSnapName);
}
@Test
- public void testValidateSnapshotName_snapshotNameWithNullChar_expectException()
+ void testValidateSnapshotName_snapshotNameWithNullChar_expectException()
{
String testSnapName = "valid" + '\0' + "snapshotname";
- HttpException httpEx = Assertions.assertThrows(HttpException.class,
- () -> instance.validateSnapshotName(testSnapName));
- assertThat(httpEx.getStatusCode()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code());
- assertThat(httpEx.getPayload()).isEqualTo("Invalid characters in snapshot name: " + testSnapName);
+ assertThatExceptionOfType(CassandraInputException.class).isThrownBy(() -> instance.validateSnapshotName(testSnapName))
+ .withMessage("Invalid characters in snapshot name: " + testSnapName);
}
@Test
@@ -196,9 +184,63 @@ public class CassandraInputValidatorTest
@ValueSource(strings = { "g", "--", "abc-124", "z", "x", "xax" })
void testInvalidTableId(String tableId)
{
- HttpException httpEx = Assertions.assertThrows(HttpException.class,
- () -> instance.validateTableId(tableId));
- assertThat(httpEx.getStatusCode()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code());
- assertThat(httpEx.getPayload()).isEqualTo("Invalid characters in table id: " + tableId);
+ assertThatExceptionOfType(CassandraInputException.class).isThrownBy(() -> instance.validateTableId(tableId))
+ .withMessage("Invalid characters in table id: " + tableId);
+ }
+
+ @Test
+ void failsOnNullComponentName()
+ {
+ assertThatNullPointerException().isThrownBy(() -> instance.validateComponentName(null))
+ .withMessage("componentName must not be null");
+ }
+
+ @Test
+ void failsOnNullRestrictedComponentName()
+ {
+ assertThatNullPointerException().isThrownBy(() -> instance.validateRestrictedComponentName(null))
+ .withMessage("componentName must not be null");
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "nb-35460-big-Filter.db", "nb-35460-big-Index.db",
+ "nb-35460-big-Summary.db", "nb-35460-big-Digest.crc32",
+ "nb-35460-big-Data.db", "nb-35460-big-CompressionInfo.db",
+ "nb-35460-big-TOC.txt", "nb-35460-big-Statistics.db" })
+ void testValidComponentNameValidation(String componentName)
+ {
+ assertThat(instance.validateComponentName(componentName)).isEqualTo(componentName);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "ks_name-table_name-nb-35460-big-Filter.db", "ks_name-table_name-nb-35460-big-Index.db",
+ "ks_name-table_name-nb-35460-big-Summary.db", "ks_name-table_name-nb-35460-big-Data.db",
+ "ks_name-table_name-nb-35460-big-CompressionInfo.db", "ks_name-table_name-nb-35460-big-TOC.txt",
+ "ks_name-table_name-nb-35460-big-Statistics.db" })
+ void testValidRestrictedComponentNameValidation(String componentName)
+ {
+ assertThat(instance.validateRestrictedComponentName(componentName)).isEqualTo(componentName);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "@nb-35460-big-Filter.db", "nb-35460-big-Index.not-valid-ending",
+ "nb-35460-big-duplicate-db.db.db", "nb*35460-big-Digest.crc32",
+ "nb-35460-big-Data-no-dot-db" })
+ void failsOnInvalidComponentName(String componentName)
+ {
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateComponentName(componentName))
+ .withMessage("Invalid component name: " + componentName);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "@nb-35460-big-Filter.db", "nb-35460-big-Index.not-valid-ending",
+ "nb-35460-big-Summary.db.db", "nb-35460-big-Digest.crc32",
+ "nb*35460-big-Data.db", "nb-35460-big-CompressionInfo-no-dot-db" })
+ void failsOnInvalidRestrictedComponentName(String componentName)
+ {
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateRestrictedComponentName(componentName))
+ .withMessage("Invalid component name: " + componentName);
}
}
diff --git a/server/src/test/java/org/apache/cassandra/sidecar/utils/FastCassandraInputValidatorTest.java b/server/src/test/java/org/apache/cassandra/sidecar/utils/FastCassandraInputValidatorTest.java
new file mode 100644
index 00000000..5767bdd9
--- /dev/null
+++ b/server/src/test/java/org/apache/cassandra/sidecar/utils/FastCassandraInputValidatorTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import org.apache.cassandra.sidecar.config.ParameterizedClassConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.ParameterizedClassConfigurationImpl;
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
+
+import static org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl.DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME;
+import static org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl.DEFAULT_ALLOWED_CHARS_FOR_NAME;
+import static org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl.DEFAULT_ALLOWED_CHARS_FOR_QUOTED_NAME;
+import static org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl.DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME;
+import static org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl.DEFAULT_FORBIDDEN_KEYSPACES;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Unit tests for {@link FastCassandraInputValidator}
+ */
+class FastCassandraInputValidatorTest extends CassandraInputValidatorTest
+{
+ @Override
+ protected CassandraInputValidator initializeInstanceForTest()
+ {
+ return new FastCassandraInputValidator();
+ }
+
+ @Test
+ void testParsingOfConfigurationValues()
+ {
+ Map<String, String> validTerminationConfig = Map.of("valid_terminations", ".abc,.def",
+ "valid_restricted_terminations", ".xml");
+ ParameterizedClassConfiguration validatorConfiguration = new ParameterizedClassConfigurationImpl("ignored", validTerminationConfig);
+ CassandraInputValidationConfiguration config = new CassandraInputValidationConfigurationImpl(validatorConfiguration,
+ DEFAULT_FORBIDDEN_KEYSPACES,
+ DEFAULT_ALLOWED_CHARS_FOR_NAME,
+ DEFAULT_ALLOWED_CHARS_FOR_QUOTED_NAME,
+ DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME,
+ DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME);
+ FastCassandraInputValidator validator = new FastCassandraInputValidator(config);
+ assertThat(validator.validTerminations).isEqualTo(List.of(".abc", ".def"));
+ assertThat(validator.validRestrictedTerminations).isEqualTo(List.of(".xml"));
+ }
+
+ @ParameterizedTest(name = "[{0}]")
+ @ValueSource(strings = { "", "\"\"",
+ "very_long_table_name_that_exceeds_two_hundred_and_twenty_two_characters_to_test_upper_" +
+ "limit_of_a_table_also_known_as_column_family_the_upper_limit_is_due_to_limitations_by_" +
+ "the_name_of_file_systems_and_it_also_includes_tid_1" })
+ void failsWithInvalidLengthForTableName(String tableName)
+ {
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateTableName(tableName))
+ .withMessageMatching("Invalid length \\d+ for table name: " + tableName);
+ }
+
+ @ParameterizedTest(name = "[{0}]")
+ @ValueSource(strings = { "", "\"\"", "keyspace_name_can_only_have_48_characters_or_it_will_fail" })
+ void failsWithInvalidLengthForKeyspaceName(String keyspace)
+ {
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateKeyspaceName(keyspace))
+ .withMessageMatching("Invalid length \\d+ for keyspace: " + keyspace);
+ }
+
+ @Test
+ void failsOnEmptyComponentName()
+ {
+ assertThatIllegalArgumentException().isThrownBy(() -> instance.validateComponentName(""))
+ .withMessage("componentName cannot be empty");
+ }
+
+ @Test
+ void failsOnEmptyRestrictedComponentName()
+ {
+ assertThatIllegalArgumentException().isThrownBy(() -> instance.validateRestrictedComponentName(""))
+ .withMessage("componentName cannot be empty");
+ }
+}
diff --git a/server/src/test/java/org/apache/cassandra/sidecar/utils/RegexBasedCassandraInputValidatorTest.java b/server/src/test/java/org/apache/cassandra/sidecar/utils/RegexBasedCassandraInputValidatorTest.java
new file mode 100644
index 00000000..f28439f9
--- /dev/null
+++ b/server/src/test/java/org/apache/cassandra/sidecar/utils/RegexBasedCassandraInputValidatorTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import org.apache.cassandra.sidecar.exceptions.CassandraInputException;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Unit tests for {@link RegexBasedCassandraInputValidator}
+ */
+class RegexBasedCassandraInputValidatorTest extends CassandraInputValidatorTest
+{
+ @Override
+ protected CassandraInputValidator initializeInstanceForTest()
+ {
+ return new RegexBasedCassandraInputValidator();
+ }
+
+ @ParameterizedTest(name = "[{0}]")
+ @ValueSource(strings = { "", "\"\"",
+ "very_long_table_name_that_exceeds_two_hundred_and_twenty_two_characters_to_test_upper_limit" +
+ "_of_a_table_also_known_as_column_family_the_upper_limit_is_due_to_limitations_by_the_name" +
+ "_of_file_systems_and_it_also_includes_tid_1" })
+ void failsWithInvalidLengthForTableName(String tableName)
+ {
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateTableName(tableName))
+ .withMessage("Invalid characters in table name: " + tableName);
+ }
+
+ @ParameterizedTest(name = "[{0}]")
+ @ValueSource(strings = { "", "\"\"", "keyspace_name_can_only_have_48_characters_or_it_will_fail" })
+ void failsWithInvalidLengthForKeyspaceName(String keyspace)
+ {
+ assertThatExceptionOfType(CassandraInputException.class)
+ .isThrownBy(() -> instance.validateKeyspaceName(keyspace))
+ .withMessageMatching("Invalid characters in keyspace: " + keyspace);
+ }
+
+ @Test
+ void failsOnEmptyComponentName()
+ {
+ assertThatIllegalArgumentException().isThrownBy(() -> instance.validateComponentName(""))
+ .withMessage("Invalid component name: ");
+ }
+
+ @Test
+ void failsOnEmptyRestrictedComponentName()
+ {
+ assertThatIllegalArgumentException().isThrownBy(() -> instance.validateRestrictedComponentName(""))
+ .withMessage("Invalid component name: ");
+ }
+}
diff --git a/server/src/test/resources/config/sidecar_file_permissions.yaml b/server/src/test/resources/config/sidecar_file_permissions.yaml
index f951e8ac..04ee506c 100644
--- a/server/src/test/resources/config/sidecar_file_permissions.yaml
+++ b/server/src/test/resources/config/sidecar_file_permissions.yaml
@@ -115,5 +115,5 @@ cassandra_input_validation:
- system_virtual_schema
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_invalid_accesscontrol_config.yaml b/server/src/test/resources/config/sidecar_invalid_accesscontrol_config.yaml
index d9633d1e..de3dcb9d 100644
--- a/server/src/test/resources/config/sidecar_invalid_accesscontrol_config.yaml
+++ b/server/src/test/resources/config/sidecar_invalid_accesscontrol_config.yaml
@@ -113,5 +113,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_invalid_client_auth.yaml b/server/src/test/resources/config/sidecar_invalid_client_auth.yaml
index 067c5ff1..9c10288f 100644
--- a/server/src/test/resources/config/sidecar_invalid_client_auth.yaml
+++ b/server/src/test/resources/config/sidecar_invalid_client_auth.yaml
@@ -128,5 +128,5 @@ cassandra_input_validation:
- system_virtual_schema
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_invalid_file_permissions.yaml b/server/src/test/resources/config/sidecar_invalid_file_permissions.yaml
index 4c7a57ea..604fd57f 100644
--- a/server/src/test/resources/config/sidecar_invalid_file_permissions.yaml
+++ b/server/src/test/resources/config/sidecar_invalid_file_permissions.yaml
@@ -115,5 +115,5 @@ cassandra_input_validation:
- system_virtual_schema
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_live_migration.yaml b/server/src/test/resources/config/sidecar_live_migration.yaml
index 5ce70fa8..51cacfdc 100644
--- a/server/src/test/resources/config/sidecar_live_migration.yaml
+++ b/server/src/test/resources/config/sidecar_live_migration.yaml
@@ -201,8 +201,8 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
live_migration:
files_to_exclude:
diff --git a/server/src/test/resources/config/sidecar_metrics.yaml b/server/src/test/resources/config/sidecar_metrics.yaml
index a216e8ba..b215d324 100644
--- a/server/src/test/resources/config/sidecar_metrics.yaml
+++ b/server/src/test/resources/config/sidecar_metrics.yaml
@@ -87,5 +87,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_metrics_empty_filters.yaml b/server/src/test/resources/config/sidecar_metrics_empty_filters.yaml
index 67a1c205..54c3f648 100644
--- a/server/src/test/resources/config/sidecar_metrics_empty_filters.yaml
+++ b/server/src/test/resources/config/sidecar_metrics_empty_filters.yaml
@@ -79,5 +79,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_missing_jmx.yaml b/server/src/test/resources/config/sidecar_missing_jmx.yaml
index 0068126e..6000ab89 100644
--- a/server/src/test/resources/config/sidecar_missing_jmx.yaml
+++ b/server/src/test/resources/config/sidecar_missing_jmx.yaml
@@ -129,5 +129,5 @@ cassandra_input_validation:
- system_virtual_schema
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_multiple_instances.yaml b/server/src/test/resources/config/sidecar_multiple_instances.yaml
index 88b17b8e..b0480fc2 100644
--- a/server/src/test/resources/config/sidecar_multiple_instances.yaml
+++ b/server/src/test/resources/config/sidecar_multiple_instances.yaml
@@ -201,5 +201,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_no_local_instances.yaml b/server/src/test/resources/config/sidecar_no_local_instances.yaml
index 10948884..c5fbcf27 100644
--- a/server/src/test/resources/config/sidecar_no_local_instances.yaml
+++ b/server/src/test/resources/config/sidecar_no_local_instances.yaml
@@ -81,5 +81,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_schema_keyspace_configuration.yaml b/server/src/test/resources/config/sidecar_schema_keyspace_configuration.yaml
index 60c96e96..518343ca 100644
--- a/server/src/test/resources/config/sidecar_schema_keyspace_configuration.yaml
+++ b/server/src/test/resources/config/sidecar_schema_keyspace_configuration.yaml
@@ -98,5 +98,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_single_instance.yaml b/server/src/test/resources/config/sidecar_single_instance.yaml
index 84b0a009..8ca49aaa 100644
--- a/server/src/test/resources/config/sidecar_single_instance.yaml
+++ b/server/src/test/resources/config/sidecar_single_instance.yaml
@@ -116,5 +116,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_single_instance_non_zero_port.yaml b/server/src/test/resources/config/sidecar_single_instance_non_zero_port.yaml
index 3e5438c7..413274fe 100644
--- a/server/src/test/resources/config/sidecar_single_instance_non_zero_port.yaml
+++ b/server/src/test/resources/config/sidecar_single_instance_non_zero_port.yaml
@@ -102,5 +102,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_ssl.yaml b/server/src/test/resources/config/sidecar_ssl.yaml
index f9926f02..dd5e8fd9 100644
--- a/server/src/test/resources/config/sidecar_ssl.yaml
+++ b/server/src/test/resources/config/sidecar_ssl.yaml
@@ -161,5 +161,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_unrecognized_authenticator.yaml b/server/src/test/resources/config/sidecar_unrecognized_authenticator.yaml
index da8a862a..16444a37 100644
--- a/server/src/test/resources/config/sidecar_unrecognized_authenticator.yaml
+++ b/server/src/test/resources/config/sidecar_unrecognized_authenticator.yaml
@@ -115,5 +115,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_unrecognized_authorizer.yaml b/server/src/test/resources/config/sidecar_unrecognized_authorizer.yaml
index 7a2655f7..c11f6ede 100644
--- a/server/src/test/resources/config/sidecar_unrecognized_authorizer.yaml
+++ b/server/src/test/resources/config/sidecar_unrecognized_authorizer.yaml
@@ -119,5 +119,5 @@ cassandra_input_validation:
- system_virtual_schema
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_validation_configuration.yaml b/server/src/test/resources/config/sidecar_validation_configuration.yaml
index 717cff7a..ed556468 100644
--- a/server/src/test/resources/config/sidecar_validation_configuration.yaml
+++ b/server/src/test/resources/config/sidecar_validation_configuration.yaml
@@ -25,11 +25,16 @@ sidecar:
execute_interval: 50ms
cassandra_input_validation:
+ validator:
+ - class_name: org.apache.cassandra.sidecar.utils.FastCassandraInputValidator
+ parameters:
+ valid_terminations: ".abc,.def"
+ valid_restricted_terminations: ".xml"
forbidden_keyspaces:
- a
- b
- c
allowed_chars_for_directory: "[a-z]+"
allowed_chars_for_quoted_name: "[A-Z]+"
- allowed_chars_for_component_name: "(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "(.db|TOC.txt)"
+ allowed_chars_for_component_name: "(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "(\\.db|TOC\\.txt)"
diff --git a/server/src/test/resources/config/sidecar_vertx_filesystem_options.yaml b/server/src/test/resources/config/sidecar_vertx_filesystem_options.yaml
index 32dc3981..c00bc9c4 100644
--- a/server/src/test/resources/config/sidecar_vertx_filesystem_options.yaml
+++ b/server/src/test/resources/config/sidecar_vertx_filesystem_options.yaml
@@ -108,5 +108,5 @@ cassandra_input_validation:
- sidecar_internal
allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}"
allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}"
- allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
- allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+ allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(\\.db|\\.cql|\\.json|\\.crc32|TOC\\.txt)"
+ allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(\\.db|TOC\\.txt)"
diff --git a/server/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java b/server/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
index ec343c81..dc0473ee 100644
--- a/server/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
+++ b/server/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
@@ -45,6 +45,7 @@ import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.config.ServiceConfiguration;
import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
+import org.apache.cassandra.sidecar.utils.FastCassandraInputValidator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -69,7 +70,7 @@ public abstract class AbstractSnapshotPathBuilderTest
@BeforeEach
protected void setup() throws IOException
{
- CassandraInputValidator validator = new CassandraInputValidator();
+ CassandraInputValidator validator = new FastCassandraInputValidator();
InstancesMetadata mockInstancesMetadata = mock(InstancesMetadata.class);
InstanceMetadata mockInstanceMeta = mock(InstanceMetadata.class);
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org