server: properly enforce validation regex

cf. #669
Previously it would incorrectly allow strings with a trailing newline.
>>> re.match(r"^\S+$", "test\n")
<re.Match object; span=(0, 4), match='test'>
This commit is contained in:
Eva
2025-03-27 17:17:09 +01:00
parent 782f069031
commit 13e102b487
7 changed files with 13 additions and 13 deletions

View File

@ -31,7 +31,7 @@ class InvalidPoolCategoryColorError(errors.ValidationError):
def _verify_name_validity(name: str) -> None: def _verify_name_validity(name: str) -> None:
name_regex = config.config["pool_category_name_regex"] name_regex = config.config["pool_category_name_regex"]
if not re.match(name_regex, name): if not re.fullmatch(name_regex, name):
raise InvalidPoolCategoryNameError( raise InvalidPoolCategoryNameError(
"Name must satisfy regex %r." % name_regex "Name must satisfy regex %r." % name_regex
) )
@ -110,7 +110,7 @@ def update_category_color(category: model.PoolCategory, color: str) -> None:
assert category assert category
if not color: if not color:
raise InvalidPoolCategoryColorError("Color cannot be empty.") raise InvalidPoolCategoryColorError("Color cannot be empty.")
if not re.match(r"^#?[0-9a-z]+$", color): if not re.fullmatch(r"^#?[0-9a-z]+$", color):
raise InvalidPoolCategoryColorError("Invalid color.") raise InvalidPoolCategoryColorError("Invalid color.")
if util.value_exceeds_column_size(color, model.PoolCategory.color): if util.value_exceeds_column_size(color, model.PoolCategory.color):
raise InvalidPoolCategoryColorError("Color is too long.") raise InvalidPoolCategoryColorError("Color is too long.")

View File

@ -48,7 +48,7 @@ def _verify_name_validity(name: str) -> None:
if util.value_exceeds_column_size(name, model.PoolName.name): if util.value_exceeds_column_size(name, model.PoolName.name):
raise InvalidPoolNameError("Name is too long.") raise InvalidPoolNameError("Name is too long.")
name_regex = config.config["pool_name_regex"] name_regex = config.config["pool_name_regex"]
if not re.match(name_regex, name): if not re.fullmatch(name_regex, name):
raise InvalidPoolNameError("Name must satisfy regex %r." % name_regex) raise InvalidPoolNameError("Name must satisfy regex %r." % name_regex)

View File

@ -31,7 +31,7 @@ class InvalidTagCategoryColorError(errors.ValidationError):
def _verify_name_validity(name: str) -> None: def _verify_name_validity(name: str) -> None:
name_regex = config.config["tag_category_name_regex"] name_regex = config.config["tag_category_name_regex"]
if not re.match(name_regex, name): if not re.fullmatch(name_regex, name):
raise InvalidTagCategoryNameError( raise InvalidTagCategoryNameError(
"Name must satisfy regex %r." % name_regex "Name must satisfy regex %r." % name_regex
) )
@ -115,7 +115,7 @@ def update_category_color(category: model.TagCategory, color: str) -> None:
assert category assert category
if not color: if not color:
raise InvalidTagCategoryColorError("Color cannot be empty.") raise InvalidTagCategoryColorError("Color cannot be empty.")
if not re.match(r"^#?[0-9a-z]+$", color): if not re.fullmatch(r"^#?[0-9a-z]+$", color):
raise InvalidTagCategoryColorError("Invalid color.") raise InvalidTagCategoryColorError("Invalid color.")
if util.value_exceeds_column_size(color, model.TagCategory.color): if util.value_exceeds_column_size(color, model.TagCategory.color):
raise InvalidTagCategoryColorError("Color is too long.") raise InvalidTagCategoryColorError("Color is too long.")

View File

@ -40,7 +40,7 @@ def _verify_name_validity(name: str) -> None:
if util.value_exceeds_column_size(name, model.TagName.name): if util.value_exceeds_column_size(name, model.TagName.name):
raise InvalidTagNameError("Name is too long.") raise InvalidTagNameError("Name is too long.")
name_regex = config.config["tag_name_regex"] name_regex = config.config["tag_name_regex"]
if not re.match(name_regex, name): if not re.fullmatch(name_regex, name):
raise InvalidTagNameError("Name must satisfy regex %r." % name_regex) raise InvalidTagNameError("Name must satisfy regex %r." % name_regex)

View File

@ -235,7 +235,7 @@ def update_user_name(user: model.User, name: str) -> None:
raise InvalidUserNameError("User name is too long.") raise InvalidUserNameError("User name is too long.")
name = name.strip() name = name.strip()
name_regex = config.config["user_name_regex"] name_regex = config.config["user_name_regex"]
if not re.match(name_regex, name): if not re.fullmatch(name_regex, name):
raise InvalidUserNameError( raise InvalidUserNameError(
"User name %r must satisfy regex %r." % (name, name_regex) "User name %r must satisfy regex %r." % (name, name_regex)
) )
@ -252,7 +252,7 @@ def update_user_password(user: model.User, password: str) -> None:
if not password: if not password:
raise InvalidPasswordError("Password cannot be empty.") raise InvalidPasswordError("Password cannot be empty.")
password_regex = config.config["password_regex"] password_regex = config.config["password_regex"]
if not re.match(password_regex, password): if not re.fullmatch(password_regex, password):
raise InvalidPasswordError( raise InvalidPasswordError(
"Password must satisfy regex %r." % password_regex "Password must satisfy regex %r." % password_regex
) )

View File

@ -84,7 +84,7 @@ def flip(source: Dict[Any, Any]) -> Dict[Any, Any]:
def is_valid_email(email: Optional[str]) -> bool: def is_valid_email(email: Optional[str]) -> bool:
"""Return whether given email address is valid or empty.""" """Return whether given email address is valid or empty."""
return not email or re.match(r"^[^@]*@[^@]*\.[^@]*$", email) is not None return not email or re.fullmatch(r"^[^@]*@[^@]*\.[^@]*$", email) is not None
class dotdict(dict): class dotdict(dict):
@ -121,12 +121,12 @@ def parse_time_range(value: str) -> Tuple[datetime, datetime]:
datetime(now.year, now.month, now.day, 0, 0, 0) - one_second, datetime(now.year, now.month, now.day, 0, 0, 0) - one_second,
) )
match = re.match(r"^(\d{4})$", value) match = re.fullmatch(r"^(\d{4})$", value)
if match: if match:
year = int(match.group(1)) year = int(match.group(1))
return (datetime(year, 1, 1), datetime(year + 1, 1, 1) - one_second) return (datetime(year, 1, 1), datetime(year + 1, 1, 1) - one_second)
match = re.match(r"^(\d{4})-(\d{1,2})$", value) match = re.fullmatch(r"^(\d{4})-(\d{1,2})$", value)
if match: if match:
year = int(match.group(1)) year = int(match.group(1))
month = int(match.group(2)) month = int(match.group(2))
@ -135,7 +135,7 @@ def parse_time_range(value: str) -> Tuple[datetime, datetime]:
datetime(year, month + 1, 1) - one_second, datetime(year, month + 1, 1) - one_second,
) )
match = re.match(r"^(\d{4})-(\d{1,2})-(\d{1,2})$", value) match = re.fullmatch(r"^(\d{4})-(\d{1,2})-(\d{1,2})$", value)
if match: if match:
year = int(match.group(1)) year = int(match.group(1))
month = int(match.group(2)) month = int(match.group(2))

View File

@ -81,7 +81,7 @@ class Parser:
negated = True negated = True
if not chunk: if not chunk:
raise errors.SearchError("Empty negated token.") raise errors.SearchError("Empty negated token.")
match = re.match(r"^(.*?)(?<!\\):(.*)$", chunk) match = re.fullmatch(r"^(.*?)(?<!\\):(.*)$", chunk)
if match: if match:
key, value = list(match.groups()) key, value = list(match.groups())
key = util.unescape(key) key = util.unescape(key)