new option for checking path/user value

This commit is contained in:
Peter Bieringer
2026-04-21 18:51:58 +02:00
parent e279e4f803
commit af5121ab34
6 changed files with 66 additions and 1 deletions

View File

@@ -1617,6 +1617,33 @@ Strict preconditions check on PUT in case item already exists [RFC6352#9.2](http
Default: `False`
##### validate_user_value
_(>= 3.7.2)_
Validate user value content
Available types are:
* `none`
* `minimal` (control and some special chars)
* `unicodeletter` (unicode letters)
* `strict` (reduced ASCII set)
Default: `minimum`
##### validate_path_type
_(>= 3.7.2)_
Validate path value content
* `none`
* `minimal` (control and some special chars)
* `unicodeletter` (unicode letters)
* `strict` (reduced ASCII set)
Default: `minimum`
##### hook
Command that is run after changes to storage. See the

8
config
View File

@@ -58,6 +58,14 @@
# script name to strip from URI if called by reverse proxy
#script_name = (default taken from HTTP_X_SCRIPT_NAME or SCRIPT_NAME)
# validate user type
# Value: none|minimal|unicodeletter|strict
#validate_user_type = minimal
# validate path value
# Value: none|minimal|unicodeletter|strict
#validate_path_type = minimal
[encoding]

View File

@@ -86,6 +86,8 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
_profiling_per_request: bool = False
_profiling_per_request_method: bool = False
_limit_content: int
_validate_user_value: str
_validate_path_value: str
profiler_per_request_method: dict[str, cProfile.Profile] = {}
profiler_per_request_method_counter: dict[str, int] = {}
profiler_per_request_method_starttime: datetime.datetime
@@ -158,6 +160,11 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
self._extra_headers[key] = configuration.get("headers", key)
self._strict_preconditions = configuration.get("storage", "strict_preconditions")
logger.info("strict preconditions check: %s", self._strict_preconditions)
# Format checks
self._validate_user_value = configuration.get("server", "validate_user_value")
self._validate_path_value = configuration.get("server", "validate_path_value")
logger.info("validate user value: %r", self._validate_user_value)
logger.info("validate path value: %r", self._validate_path_value)
# Profiling options
self._profiling = configuration.get("logging", "profiling")
self._profiling_per_request_min_duration = configuration.get("logging", "profiling_per_request_min_duration")

View File

@@ -44,6 +44,8 @@ class ApplicationBase:
_permit_delete_collection: bool
_permit_overwrite_collection: bool
_strict_preconditions: bool
_validate_user_value: str
_validate_path_format: str
_hook: hook.BaseHook
def __init__(self, configuration: config.Configuration) -> None:
@@ -58,6 +60,8 @@ class ApplicationBase:
self._response_content_on_debug = configuration.get("logging", "response_content_on_debug")
self._request_content_on_debug = configuration.get("logging", "request_content_on_debug")
self._limit_content = configuration.get("logging", "limit_content")
self._validate_user_value = configuration.get("server", "validate_user_value")
self._validate_path_value = configuration.get("server", "validate_path_value")
self._hook = hook.load(configuration)
def _read_xml_request_body(self, environ: types.WSGIEnviron

View File

@@ -48,6 +48,8 @@ DEFAULT_CONFIG_PATH: str = os.pathsep.join([
PROFILING: Sequence[str] = ("per_request", "per_request_method", "none")
VALIDATE_TYPES: Sequence[str] = ("none", "minimal", "unicodeletter", "strict")
def positive_int(value: Any) -> int:
value = int(value)
@@ -84,6 +86,12 @@ def logging_level(value: Any) -> str:
return value
def validate_types(value: Any) -> str:
if value not in VALIDATE_TYPES:
raise ValueError("unsupported validation type: %r" % value)
return value
def profiling(value: Any) -> str:
if value not in PROFILING:
raise ValueError("unsupported profiling: %r" % value)
@@ -221,6 +229,14 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
"value": "",
"help": "script name to strip from URI if called by reverse proxy (default taken from HTTP_X_SCRIPT_NAME or SCRIPT_NAME)",
"type": str}),
("validate_user_value", {
"value": "minimal",
"help": "validate user value (" + "|".join(VALIDATE_TYPES) + ")",
"type": validate_types}),
("validate_path_value", {
"value": "minimal",
"help": "validate path value (" + "|".join(VALIDATE_TYPES) + ")",
"type": validate_types}),
("_internal_server", {
"value": "False",
"help": "the internal server is used",

View File

@@ -29,6 +29,7 @@ from urllib.parse import parse_qs
from radicale import (config, httputils, pathutils, rights, storage, types,
utils)
from radicale.app.base import ApplicationBase
from radicale.log import logger
INTERNAL_TYPES: Sequence[str] = ("csv", "files", "none")
@@ -135,7 +136,7 @@ def load(configuration: "config.Configuration") -> "BaseSharing":
return utils.load_plugin(INTERNAL_TYPES, "sharing", "Sharing", BaseSharing, configuration)
class BaseSharing:
class BaseSharing(ApplicationBase):
_storage: storage.BaseStorage
_rights: rights.BaseRights
@@ -157,6 +158,8 @@ class BaseSharing:
self._rights = rights.load(configuration)
self._storage = storage.load(configuration)
self._auth_delay = configuration.get("auth", "delay")
self._validate_user_value = configuration.get("server", "validate_user_value")
self._validate_path_value = configuration.get("server", "validate_path_value")
# Sharing
self.sharing_collection_by_map = configuration.get("sharing", "collection_by_map")
self.sharing_collection_by_token = configuration.get("sharing", "collection_by_token")