@@ -70,6 +70,7 @@ def __init__(self):
70
70
'query' : False ,
71
71
'fragment' : False ,
72
72
}
73
+ self .validated_components = self .required_components .copy ()
73
74
74
75
def allow_schemes (self , * schemes ):
75
76
"""Require the scheme to be one of the provided schemes.
@@ -147,9 +148,36 @@ def forbid_use_of_password(self):
147
148
self .allow_password = False
148
149
return self
149
150
151
+ def check_validity_of (self , * components ):
152
+ """Check the validity of the components provided.
153
+
154
+ This can be specified repeatedly.
155
+
156
+ .. versionadded:: 1.1
157
+
158
+ :param components:
159
+ Names of components from :attr:`Validator.COMPONENT_NAMES`.
160
+ :returns:
161
+ The validator instance.
162
+ :rtype:
163
+ Validator
164
+ """
165
+ components = [c .lower () for c in components ]
166
+ for component in components :
167
+ if component not in self .COMPONENT_NAMES :
168
+ raise ValueError (
169
+ '"{}" is not a valid component' .format (component )
170
+ )
171
+ self .validated_components .update ({
172
+ component : True for component in components
173
+ })
174
+ return self
175
+
150
176
def require_presence_of (self , * components ):
151
177
"""Require the components provided.
152
178
179
+ This can be specified repeatedly.
180
+
153
181
.. versionadded:: 1.0
154
182
155
183
:param components:
@@ -186,6 +214,8 @@ def validate(self, uri):
186
214
:raises PasswordForbidden:
187
215
When a password is present in the userinfo component but is
188
216
not permitted by configuration.
217
+ :raises InvalidComponentsError:
218
+ When a component was found to be invalid.
189
219
"""
190
220
if not self .allow_password :
191
221
check_password (uri )
@@ -195,8 +225,15 @@ def validate(self, uri):
195
225
for component , required in self .required_components .items ()
196
226
if required
197
227
]
228
+ validated_components = [
229
+ component
230
+ for component , required in self .validated_components .items ()
231
+ if required
232
+ ]
198
233
if required_components :
199
234
ensure_required_components_exist (uri , required_components )
235
+ if validated_components :
236
+ ensure_components_are_valid (uri , validated_components )
200
237
201
238
ensure_one_of (self .allowed_schemes , uri , 'scheme' )
202
239
ensure_one_of (self .allowed_hosts , uri , 'host' )
@@ -337,3 +374,52 @@ def valid_ipv4_host_address(host):
337
374
# If the host exists, and it might be IPv4, check each byte in the
338
375
# address.
339
376
return all ([0 <= int (byte , base = 10 ) <= 255 for byte in host .split ('.' )])
377
+
378
+
379
+ _COMPONENT_VALIDATORS = {
380
+ 'scheme' : scheme_is_valid ,
381
+ 'path' : path_is_valid ,
382
+ 'query' : query_is_valid ,
383
+ 'fragment' : fragment_is_valid ,
384
+ }
385
+
386
+ _SUBAUTHORITY_VALIDATORS = set (['userinfo' , 'host' , 'port' ])
387
+
388
+
389
+ def subauthority_component_is_valid (uri , component ):
390
+ """Determine if the userinfo, host, and port are valid."""
391
+ try :
392
+ subauthority_dict = uri .authority_info ()
393
+ except exceptions .InvalidAuthority :
394
+ return False
395
+
396
+ # If we can parse the authority into sub-components and we're not
397
+ # validating the port, we can assume it's valid.
398
+ if component != 'port' :
399
+ return True
400
+
401
+ try :
402
+ port = int (subauthority_dict ['port' ])
403
+ except TypeError :
404
+ # If the port wasn't provided it'll be None and int(None) raises a
405
+ # TypeError
406
+ return True
407
+
408
+ return (0 <= port <= 65535 )
409
+
410
+
411
+ def ensure_components_are_valid (uri , validated_components ):
412
+ """Assert that all components are valid in the URI."""
413
+ invalid_components = set ([])
414
+ for component in validated_components :
415
+ if component in _SUBAUTHORITY_VALIDATORS :
416
+ if not subauthority_component_is_valid (uri , component ):
417
+ invalid_components .add (component )
418
+ continue
419
+
420
+ validator = _COMPONENT_VALIDATORS [component ]
421
+ if not validator (getattr (uri , component )):
422
+ invalid_components .add (component )
423
+
424
+ if invalid_components :
425
+ raise exceptions .InvalidComponentsError (uri , * invalid_components )
0 commit comments