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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]