@@ -21,7 +21,16 @@ class URIValidator(URLValidator):
2121class AllowedURIValidator (URIValidator ):
2222 # TODO: find a way to get these associated with their form fields in place of passing name
2323 # 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+ ):
2534 """
2635 :param schemes: List of allowed schemes. E.g.: ["https"]
2736 :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
3443 self .allow_path = allow_path
3544 self .allow_query = allow_query
3645 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
3748
3849 def __call__ (self , value ):
3950 value = force_str (value )
@@ -68,8 +79,54 @@ def __call__(self, value):
6879 params = {"name" : self .name , "value" : value , "cause" : "path not allowed" },
6980 )
7081
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+
71128 try :
72- super ().__call__ (value )
129+ super ().__call__ (reassambled_uri )
73130 except ValidationError as e :
74131 raise ValidationError (
75132 "%(name)s URI validation error. %(cause)s: %(value)s" ,
0 commit comments