diff --git a/savepointradio/core/utils.py b/savepointradio/core/utils.py index f9af9b0..0d904db 100644 --- a/savepointradio/core/utils.py +++ b/savepointradio/core/utils.py @@ -17,6 +17,7 @@ from django.db import connection from django.utils.encoding import iri_to_uri, uri_to_iri from .models import Setting +from .validators import GROUP_NT_DRIVE_LETTER, GROUP_NT_UNC def generate_password(length=32): @@ -165,7 +166,7 @@ def iri_to_path(iri): # Drive letter IRI will have three slashes followed by the drive letter # UNC path IRI will have two slashes followed by the UNC path uri = iri_to_uri(iri) - patt = r'^(?:file:///[A-Za-z]:/|file://[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+/)' + patt = r'^(?:' + GROUP_NT_DRIVE_LETTER + r'|' + GROUP_NT_UNC + r')' windows = re.match(patt, uri) if windows: parse = urlparse(uri) diff --git a/savepointradio/core/validators.py b/savepointradio/core/validators.py new file mode 100644 index 0000000..6030762 --- /dev/null +++ b/savepointradio/core/validators.py @@ -0,0 +1,58 @@ +''' +Custom Django model/form field validators for the Save Point Radio project. +''' + +import re + +from django.core import validators +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + + +GROUP_NT_UNC = r'file://[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+/' + +GROUP_NT_DRIVE_LETTER = r'file:///[A-Za-z](?:\:|\|)/' + +GROUP_NON_AUTH = r'file:///[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+' + +FILE_IRI_PATTERN = ( + r'^(?P' + + GROUP_NT_UNC + + r')|(?P' + + GROUP_NT_DRIVE_LETTER + + r')|(?P' + + GROUP_NON_AUTH + + r')' +) + + +class RadioIRIValidator(validators.URLValidator): + ''' + Validates an RFC3987-defined IRI along with RFC8089 for file:// and other + custom schemes. + ''' + + message = _('Enter a valid IRI.') + schemes = ['http', 'https', 'file', 'ftp', 'ftps', 's3'] + + def __init__(self, schemes=None, **kwargs): + super().__init__(**kwargs) + if schemes is not None: + self.schemes = schemes + + def __call__(self, value): + # Check the schemes first + scheme = value.split('://')[0].lower() + if scheme not in self.schemes: + raise ValidationError(self.message, code=self.code) + + # Ignore the non-standard IRI + if scheme == 'file': + pattern = re.compile(FILE_IRI_PATTERN) + if not pattern.match(value): + raise ValidationError(self.message, code=self.code) + elif scheme == 's3': + # Nothing to validate, really. . . + return + else: + super().__call__(value)