21
21
class Validator (object ):
22
22
"""Object used to configure validation of all objects in rfc3986.
23
23
24
+ .. versionadded:: 1.0
25
+
24
26
Example usage::
25
27
26
28
>>> from rfc3986 import api, validators
@@ -68,10 +70,13 @@ def __init__(self):
68
70
'query' : False ,
69
71
'fragment' : False ,
70
72
}
73
+ self .validated_components = self .required_components .copy ()
71
74
72
75
def allow_schemes (self , * schemes ):
73
76
"""Require the scheme to be one of the provided schemes.
74
77
78
+ .. versionadded:: 1.0
79
+
75
80
:param schemes:
76
81
Schemes, without ``://`` that are allowed.
77
82
:returns:
@@ -86,6 +91,8 @@ def allow_schemes(self, *schemes):
86
91
def allow_hosts (self , * hosts ):
87
92
"""Require the host to be one of the provided hosts.
88
93
94
+ .. versionadded:: 1.0
95
+
89
96
:param hosts:
90
97
Hosts that are allowed.
91
98
:returns:
@@ -100,6 +107,8 @@ def allow_hosts(self, *hosts):
100
107
def allow_ports (self , * ports ):
101
108
"""Require the port to be one of the provided ports.
102
109
110
+ .. versionadded:: 1.0
111
+
103
112
:param ports:
104
113
Ports that are allowed.
105
114
:returns:
@@ -114,18 +123,63 @@ def allow_ports(self, *ports):
114
123
return self
115
124
116
125
def allow_use_of_password (self ):
117
- """Allow passwords to be present in the URI."""
126
+ """Allow passwords to be present in the URI.
127
+
128
+ .. versionadded:: 1.0
129
+
130
+ :returns:
131
+ The validator instance.
132
+ :rtype:
133
+ Validator
134
+ """
118
135
self .allow_password = True
119
136
return self
120
137
121
138
def forbid_use_of_password (self ):
122
- """Prevent passwords from being included in the URI."""
139
+ """Prevent passwords from being included in the URI.
140
+
141
+ .. versionadded:: 1.0
142
+
143
+ :returns:
144
+ The validator instance.
145
+ :rtype:
146
+ Validator
147
+ """
123
148
self .allow_password = False
124
149
return self
125
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
+
126
176
def require_presence_of (self , * components ):
127
177
"""Require the components provided.
128
178
179
+ This can be specified repeatedly.
180
+
181
+ .. versionadded:: 1.0
182
+
129
183
:param components:
130
184
Names of components from :attr:`Validator.COMPONENT_NAMES`.
131
185
:returns:
@@ -147,6 +201,8 @@ def require_presence_of(self, *components):
147
201
def validate (self , uri ):
148
202
"""Check a URI for conditions specified on this validator.
149
203
204
+ .. versionadded:: 1.0
205
+
150
206
:param uri:
151
207
Parsed URI to validate.
152
208
:type uri:
@@ -158,6 +214,8 @@ def validate(self, uri):
158
214
:raises PasswordForbidden:
159
215
When a password is present in the userinfo component but is
160
216
not permitted by configuration.
217
+ :raises InvalidComponentsError:
218
+ When a component was found to be invalid.
161
219
"""
162
220
if not self .allow_password :
163
221
check_password (uri )
@@ -167,8 +225,15 @@ def validate(self, uri):
167
225
for component , required in self .required_components .items ()
168
226
if required
169
227
]
228
+ validated_components = [
229
+ component
230
+ for component , required in self .validated_components .items ()
231
+ if required
232
+ ]
170
233
if required_components :
171
234
ensure_required_components_exist (uri , required_components )
235
+ if validated_components :
236
+ ensure_components_are_valid (uri , validated_components )
172
237
173
238
ensure_one_of (self .allowed_schemes , uri , 'scheme' )
174
239
ensure_one_of (self .allowed_hosts , uri , 'host' )
@@ -309,3 +374,55 @@ def valid_ipv4_host_address(host):
309
374
# If the host exists, and it might be IPv4, check each byte in the
310
375
# address.
311
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
+ # Python's peephole optimizer means that while this continue *is*
419
+ # actually executed, coverage.py cannot detect that. See also,
420
+ # https://bitbucket.org/ned/coveragepy/issues/198/continue-marked-as-not-covered
421
+ continue # nocov: Python 2.7, 3.3, 3.4
422
+
423
+ validator = _COMPONENT_VALIDATORS [component ]
424
+ if not validator (getattr (uri , component )):
425
+ invalid_components .add (component )
426
+
427
+ if invalid_components :
428
+ raise exceptions .InvalidComponentsError (uri , * invalid_components )
0 commit comments