@@ -21,7 +21,16 @@ class URIValidator(URLValidator):
21
21
class AllowedURIValidator (URIValidator ):
22
22
# TODO: find a way to get these associated with their form fields in place of passing name
23
23
# TODO: submit PR to get `cause` included in the parent class ValidationError params`
24
- def __init__ (self , schemes , name , allow_path = False , allow_query = False , allow_fragments = False ):
24
+ def __init__ (
25
+ self ,
26
+ schemes ,
27
+ name ,
28
+ allow_path = False ,
29
+ allow_query = False ,
30
+ allow_fragments = False ,
31
+ allow_hostname_wildcard = False ,
32
+ allow_path_wildcard = False ,
33
+ ):
25
34
"""
26
35
:param schemes: List of allowed schemes. E.g.: ["https"]
27
36
:param name: Name of the validated URI. It is required for validation message. E.g.: "Origin"
@@ -34,6 +43,8 @@ def __init__(self, schemes, name, allow_path=False, allow_query=False, allow_fra
34
43
self .allow_path = allow_path
35
44
self .allow_query = allow_query
36
45
self .allow_fragments = allow_fragments
46
+ self .allow_hostname_wildcard = allow_hostname_wildcard or True
47
+ self .allow_path_wildcard = allow_path_wildcard or True
37
48
38
49
def __call__ (self , value ):
39
50
value = force_str (value )
@@ -68,8 +79,54 @@ def __call__(self, value):
68
79
params = {"name" : self .name , "value" : value , "cause" : "path not allowed" },
69
80
)
70
81
82
+ if self .allow_hostname_wildcard and "*" in netloc :
83
+ domain_parts = netloc .split ("." )
84
+ if netloc .count ("*" ) > 1 :
85
+ raise ValidationError (
86
+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
87
+ params = {"name" : self .name , "value" : value , "cause" : "only one wildcard is allowed in the hostname" },
88
+ )
89
+ if not netloc .startswith ("*" ):
90
+ raise ValidationError (
91
+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
92
+ params = {"name" : self .name , "value" : value , "cause" : "wildcards must be at the beginning of the hostname" },
93
+ )
94
+ if len (domain_parts ) < 3 :
95
+ raise ValidationError (
96
+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
97
+ params = {"name" : self .name , "value" : value , "cause" : "wildcards cannot be in the top level or second level domain" },
98
+ )
99
+
100
+ # strip the wildcard from the netloc, we'll reassamble the value later to pass to URI Validator
101
+ if netloc .startswith ("*." ):
102
+ netloc = netloc [2 :]
103
+ else :
104
+ netloc = netloc [1 :]
105
+
106
+ if self .allow_path_wildcard and "*" in path :
107
+ if path .count ("*" ) > 1 :
108
+ raise ValidationError (
109
+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
110
+ params = {"name" : self .name , "value" : value , "cause" : "only one wildcard is allowed in the path" },
111
+ )
112
+ if not path .endswith ("*" ):
113
+ raise ValidationError (
114
+ "%(name)s URI validation error. %(cause)s: %(value)s" ,
115
+ params = {"name" : self .name , "value" : value , "cause" : "wildcards must be at the end of the path" },
116
+ )
117
+ # strip the wildcard from the path, we'll reassamble the value later to pass to URI Validator
118
+ path = path [:- 1 ]
119
+
120
+ # we stripped the wildcard from the netloc and path if they were allowed and present since they would
121
+ # fail validation we'll reassamble the URI to pass to the URIValidator
122
+ reassambled_uri = f"{ scheme } ://{ netloc } { path } "
123
+ if query :
124
+ reassambled_uri += f"?{ query } "
125
+ if fragment :
126
+ reassambled_uri += f"#{ fragment } "
127
+
71
128
try :
72
- super ().__call__ (value )
129
+ super ().__call__ (reassambled_uri )
73
130
except ValidationError as e :
74
131
raise ValidationError (
75
132
"%(name)s URI validation error. %(cause)s: %(value)s" ,
0 commit comments