| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -169,8 +169,8 @@ The following exceptions are raised as appropriate: | |||
| 169 | 169 | A subclass of :exc:`HTTPException`. Raised if a server responds with a HTTP | |
| 170 | 170 | status code that we don't understand. | |
| 171 | 171 | ||
| 172 | - The constants defined in this module are: | ||
| 173 | 172 | ||
| 173 | + The constants defined in this module are: | ||
| 174 | 174 | ||
| 175 | 175 | .. data:: HTTP_PORT | |
| 176 | 176 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -286,10 +286,10 @@ Certificate handling | |||
| 286 | 286 | Verify that *cert* (in decoded format as returned by | |
| 287 | 287 | :meth:`SSLSocket.getpeercert`) matches the given *hostname*. The rules | |
| 288 | 288 | applied are those for checking the identity of HTTPS servers as outlined | |
| 289 | - in :rfc:`2818`, except that IP addresses are not currently supported. | ||
| 290 | - In addition to HTTPS, this function should be suitable for checking the | ||
| 291 | - identity of servers in various SSL-based protocols such as FTPS, IMAPS, | ||
| 292 | - POPS and others. | ||
| 289 | + in :rfc:`2818` and :rfc:`6125`, except that IP addresses are not currently | ||
| 290 | + supported. In addition to HTTPS, this function should be suitable for | ||
| 291 | + checking the identity of servers in various SSL-based protocols such as | ||
| 292 | + FTPS, IMAPS, POPS and others. | ||
| 293 | 293 | ||
| 294 | 294 | :exc:`CertificateError` is raised on failure. On success, the function | |
| 295 | 295 | returns nothing:: | |
@@ -304,6 +304,13 @@ Certificate handling | |||
| 304 | 304 | ||
| 305 | 305 | .. versionadded:: 3.2 | |
| 306 | 306 | ||
| 307 | + .. versionchanged:: 3.3.3 | ||
| 308 | + The function now follows :rfc:`6125`, section 6.4.3 and does neither | ||
| 309 | + match multiple wildcards (e.g. ``*.*.com`` or ``*a*.example.org``) nor | ||
| 310 | + a wildcard inside an internationalized domain names (IDN) fragment. | ||
| 311 | + IDN A-labels such as ``www*.xn--pthon-kva.org`` are still supported, | ||
| 312 | + but ``x*.python.org`` no longer matches ``xn--tda.python.org``. | ||
| 313 | + | ||
| 307 | 314 | .. function:: cert_time_to_seconds(timestring) | |
| 308 | 315 | ||
| 309 | 316 | Returns a floating-point value containing a normal seconds-after-the-epoch | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -214,6 +214,8 @@ | |||
| 214 | 214 | ||
| 215 | 215 | # maximal line length when calling readline(). | |
| 216 | 216 | _MAXLINE = 65536 | |
| 217 | + _MAXHEADERS = 100 | ||
| 218 | + | ||
| 217 | 219 | ||
| 218 | 220 | class HTTPMessage(email.message.Message): | |
| 219 | 221 | # XXX The only usage of this method is in | |
@@ -261,6 +263,8 @@ def parse_headers(fp, _class=HTTPMessage): | |||
| 261 | 263 | if len(line) > _MAXLINE: | |
| 262 | 264 | raise LineTooLong("header line") | |
| 263 | 265 | headers.append(line) | |
| 266 | + if len(headers) > _MAXHEADERS: | ||
| 267 | + raise HTTPException("got more than %d headers" % _MAXHEADERS) | ||
| 264 | 268 | if line in (b'\r\n', b'\n', b''): | |
| 265 | 269 | break | |
| 266 | 270 | hstring = b''.join(headers).decode('iso-8859-1') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,6 +43,15 @@ | |||
| 43 | 43 | IMAP4_SSL_PORT = 993 | |
| 44 | 44 | AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first | |
| 45 | 45 | ||
| 46 | + # Maximal line length when calling readline(). This is to prevent | ||
| 47 | + # reading arbitrary length lines. RFC 3501 and 2060 (IMAP 4rev1) | ||
| 48 | + # don't specify a line length. RFC 2683 however suggests limiting client | ||
| 49 | + # command lines to 1000 octets and server command lines to 8000 octets. | ||
| 50 | + # We have selected 10000 for some extra margin and since that is supposedly | ||
| 51 | + # also what UW and Panda IMAP does. | ||
| 52 | + _MAXLINE = 10000 | ||
| 53 | + | ||
| 54 | + | ||
| 46 | 55 | # Commands | |
| 47 | 56 | ||
| 48 | 57 | Commands = { | |
@@ -256,7 +265,10 @@ def read(self, size): | |||
| 256 | 265 | ||
| 257 | 266 | def readline(self): | |
| 258 | 267 | """Read line from remote.""" | |
| 259 | - return self.file.readline() | ||
| 268 | + line = self.file.readline(_MAXLINE + 1) | ||
| 269 | + if len(line) > _MAXLINE: | ||
| 270 | + raise self.error("got more than %d bytes" % _MAXLINE) | ||
| 271 | + return line | ||
| 260 | 272 | ||
| 261 | 273 | ||
| 262 | 274 | def send(self, data): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -85,6 +85,13 @@ | |||
| 85 | 85 | "decode_header", | |
| 86 | 86 | ] | |
| 87 | 87 | ||
| 88 | + # maximal line length when calling readline(). This is to prevent | ||
| 89 | + # reading arbitrary lenght lines. RFC 3977 limits NNTP line length to | ||
| 90 | + # 512 characters, including CRLF. We have selected 2048 just to be on | ||
| 91 | + # the safe side. | ||
| 92 | + _MAXLINE = 2048 | ||
| 93 | + | ||
| 94 | + | ||
| 88 | 95 | # Exceptions raised when an error or invalid response is received | |
| 89 | 96 | class NNTPError(Exception): | |
| 90 | 97 | """Base class for all nntplib exceptions""" | |
@@ -424,7 +431,9 @@ def _getline(self, strip_crlf=True): | |||
| 424 | 431 | """Internal: return one line from the server, stripping _CRLF. | |
| 425 | 432 | Raise EOFError if the connection is closed. | |
| 426 | 433 | Returns a bytes object.""" | |
| 427 | - line = self.file.readline() | ||
| 434 | + line = self.file.readline(_MAXLINE +1) | ||
| 435 | + if len(line) > _MAXLINE: | ||
| 436 | + raise NNTPDataError('line too long') | ||
| 428 | 437 | if self.debugging > 1: | |
| 429 | 438 | print('*get*', repr(line)) | |
| 430 | 439 | if not line: raise EOFError | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -40,6 +40,12 @@ class error_proto(Exception): pass | |||
| 40 | 40 | LF = b'\n' | |
| 41 | 41 | CRLF = CR+LF | |
| 42 | 42 | ||
| 43 | + # maximal line length when calling readline(). This is to prevent | ||
| 44 | + # reading arbitrary lenght lines. RFC 1939 limits POP3 line length to | ||
| 45 | + # 512 characters, including CRLF. We have selected 2048 just to be on | ||
| 46 | + # the safe side. | ||
| 47 | + _MAXLINE = 2048 | ||
| 48 | + | ||
| 43 | 49 | ||
| 44 | 50 | class POP3: | |
| 45 | 51 | ||
@@ -118,7 +124,10 @@ def _putcmd(self, line): | |||
| 118 | 124 | # Raise error_proto('-ERR EOF') if the connection is closed. | |
| 119 | 125 | ||
| 120 | 126 | def _getline(self): | |
| 121 | - line = self.file.readline() | ||
| 127 | + line = self.file.readline(_MAXLINE + 1) | ||
| 128 | + if len(line) > _MAXLINE: | ||
| 129 | + raise error_proto('line too long') | ||
| 130 | + | ||
| 122 | 131 | if self._debugging > 1: print('*get*', repr(line)) | |
| 123 | 132 | if not line: raise error_proto('-ERR EOF') | |
| 124 | 133 | octets = len(line) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -166,31 +166,59 @@ class CertificateError(ValueError): | |||
| 166 | 166 | pass | |
| 167 | 167 | ||
| 168 | 168 | ||
| 169 | - def _dnsname_to_pat(dn, max_wildcards=1): | ||
| 169 | + def _dnsname_match(dn, hostname, max_wildcards=1): | ||
| 170 | + """Matching according to RFC 6125, section 6.4.3 | ||
| 171 | + | ||
| 172 | + http://tools.ietf.org/html/rfc6125#section-6.4.3 | ||
| 173 | + """ | ||
| 170 | 174 | pats = [] | |
| 171 | - for frag in dn.split(r'.'): | ||
| 172 | - if frag.count('*') > max_wildcards: | ||
| 173 | - # Issue #17980: avoid denials of service by refusing more | ||
| 174 | - # than one wildcard per fragment. A survey of established | ||
| 175 | - # policy among SSL implementations showed it to be a | ||
| 176 | - # reasonable choice. | ||
| 177 | - raise CertificateError( | ||
| 178 | - "too many wildcards in certificate DNS name: " + repr(dn)) | ||
| 179 | - if frag == '*': | ||
| 180 | - # When '*' is a fragment by itself, it matches a non-empty dotless | ||
| 181 | - # fragment. | ||
| 182 | - pats.append('[^.]+') | ||
| 183 | - else: | ||
| 184 | - # Otherwise, '*' matches any dotless fragment. | ||
| 185 | - frag = re.escape(frag) | ||
| 186 | - pats.append(frag.replace(r'\*', '[^.]*')) | ||
| 187 | - return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) | ||
| 175 | + if not dn: | ||
| 176 | + return False | ||
| 177 | + | ||
| 178 | + leftmost, *remainder = dn.split(r'.') | ||
| 179 | + | ||
| 180 | + wildcards = leftmost.count('*') | ||
| 181 | + if wildcards > max_wildcards: | ||
| 182 | + # Issue #17980: avoid denials of service by refusing more | ||
| 183 | + # than one wildcard per fragment. A survery of established | ||
| 184 | + # policy among SSL implementations showed it to be a | ||
| 185 | + # reasonable choice. | ||
| 186 | + raise CertificateError( | ||
| 187 | + "too many wildcards in certificate DNS name: " + repr(dn)) | ||
| 188 | + | ||
| 189 | + # speed up common case w/o wildcards | ||
| 190 | + if not wildcards: | ||
| 191 | + return dn.lower() == hostname.lower() | ||
| 192 | + | ||
| 193 | + # RFC 6125, section 6.4.3, subitem 1. | ||
| 194 | + # The client SHOULD NOT attempt to match a presented identifier in which | ||
| 195 | + # the wildcard character comprises a label other than the left-most label. | ||
| 196 | + if leftmost == '*': | ||
| 197 | + # When '*' is a fragment by itself, it matches a non-empty dotless | ||
| 198 | + # fragment. | ||
| 199 | + pats.append('[^.]+') | ||
| 200 | + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): | ||
| 201 | + # RFC 6125, section 6.4.3, subitem 3. | ||
| 202 | + # The client SHOULD NOT attempt to match a presented identifier | ||
| 203 | + # where the wildcard character is embedded within an A-label or | ||
| 204 | + # U-label of an internationalized domain name. | ||
| 205 | + pats.append(re.escape(leftmost)) | ||
| 206 | + else: | ||
| 207 | + # Otherwise, '*' matches any dotless string, e.g. www* | ||
| 208 | + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) | ||
| 209 | + | ||
| 210 | + # add the remaining fragments, ignore any wildcards | ||
| 211 | + for frag in remainder: | ||
| 212 | + pats.append(re.escape(frag)) | ||
| 213 | + | ||
| 214 | + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) | ||
| 215 | + return pat.match(hostname) | ||
| 188 | 216 | ||
| 189 | 217 | ||
| 190 | 218 | def match_hostname(cert, hostname): | |
| 191 | 219 | """Verify that *cert* (in decoded format as returned by | |
| 192 | - SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules | ||
| 193 | - are mostly followed, but IP addresses are not accepted for *hostname*. | ||
| 220 | + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 | ||
| 221 | + rules are followed, but IP addresses are not accepted for *hostname*. | ||
| 194 | 222 | ||
| 195 | 223 | CertificateError is raised on failure. On success, the function | |
| 196 | 224 | returns nothing. | |
@@ -201,7 +229,7 @@ def match_hostname(cert, hostname): | |||
| 201 | 229 | san = cert.get('subjectAltName', ()) | |
| 202 | 230 | for key, value in san: | |
| 203 | 231 | if key == 'DNS': | |
| 204 | - if _dnsname_to_pat(value).match(hostname): | ||
| 232 | + if _dnsname_match(value, hostname): | ||
| 205 | 233 | return | |
| 206 | 234 | dnsnames.append(value) | |
| 207 | 235 | if not dnsnames: | |
@@ -212,7 +240,7 @@ def match_hostname(cert, hostname): | |||
| 212 | 240 | # XXX according to RFC 2818, the most specific Common Name | |
| 213 | 241 | # must be used. | |
| 214 | 242 | if key == 'commonName': | |
| 215 | - if _dnsname_to_pat(value).match(hostname): | ||
| 243 | + if _dnsname_match(value, hostname): | ||
| 216 | 244 | return | |
| 217 | 245 | dnsnames.append(value) | |
| 218 | 246 | if len(dnsnames) > 1: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -347,6 +347,15 @@ def test_readinto_head(self): | |||
| 347 | 347 | self.fail("Did not expect response from HEAD request") | |
| 348 | 348 | self.assertEqual(bytes(b), b'\x00'*5) | |
| 349 | 349 | ||
| 350 | + def test_too_many_headers(self): | ||
| 351 | + headers = '\r\n'.join('Header%d: foo' % i | ||
| 352 | + for i in range(client._MAXHEADERS + 1)) + '\r\n' | ||
| 353 | + text = ('HTTP/1.1 200 OK\r\n' + headers) | ||
| 354 | + s = FakeSocket(text) | ||
| 355 | + r = client.HTTPResponse(s) | ||
| 356 | + self.assertRaisesRegex(client.HTTPException, | ||
| 357 | + r"got more than \d+ headers", r.begin) | ||
| 358 | + | ||
| 350 | 359 | def test_send_file(self): | |
| 351 | 360 | expected = (b'GET /foo HTTP/1.1\r\nHost: example.com\r\n' | |
| 352 | 361 | b'Accept-Encoding: identity\r\nContent-Length:') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -325,6 +325,17 @@ def cmd_AUTHENTICATE(self, tag, args): | |||
| 325 | 325 | self.assertEqual(ret, "OK") | |
| 326 | 326 | ||
| 327 | 327 | ||
| 328 | + def test_linetoolong(self): | ||
| 329 | + class TooLongHandler(SimpleIMAPHandler): | ||
| 330 | + def handle(self): | ||
| 331 | + # Send a very long response line | ||
| 332 | + self.wfile.write(b'* OK ' + imaplib._MAXLINE*b'x' + b'\r\n') | ||
| 333 | + | ||
| 334 | + with self.reaped_server(TooLongHandler) as server: | ||
| 335 | + self.assertRaises(imaplib.IMAP4.error, | ||
| 336 | + self.imap_class, *server.server_address) | ||
| 337 | + | ||
| 338 | + | ||
| 328 | 339 | class ThreadedNetworkedTests(BaseThreadedNetworkedTests): | |
| 329 | 340 | ||
| 330 | 341 | server_class = socketserver.TCPServer | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -584,6 +584,11 @@ def handle_NEWNEWS(self, group, date_str, time_str): | |||
| 584 | 584 | <a4929a40-6328-491a-aaaf-cb79ed7309a2@q2g2000vbk.googlegroups.com> | |
| 585 | 585 | <f30c0419-f549-4218-848f-d7d0131da931@y3g2000vbm.googlegroups.com> | |
| 586 | 586 | .""") | |
| 587 | + elif (group == 'comp.lang.python' and | ||
| 588 | + date_str in ('20100101', '100101') and | ||
| 589 | + time_str == '090000'): | ||
| 590 | + self.push_lit('too long line' * 3000 + | ||
| 591 | + '\n.') | ||
| 587 | 592 | else: | |
| 588 | 593 | self.push_lit("""\ | |
| 589 | 594 | 230 An empty list of newsarticles follows | |
@@ -1179,6 +1184,11 @@ def test_ihave(self): | |||
| 1179 | 1184 | self.assertEqual(cm.exception.response, | |
| 1180 | 1185 | "435 Article not wanted") | |
| 1181 | 1186 | ||
| 1187 | + def test_too_long_lines(self): | ||
| 1188 | + dt = datetime.datetime(2010, 1, 1, 9, 0, 0) | ||
| 1189 | + self.assertRaises(nntplib.NNTPDataError, | ||
| 1190 | + self.server.newnews, "comp.lang.python", dt) | ||
| 1191 | + | ||
| 1182 | 1192 | ||
| 1183 | 1193 | class NNTPv1Tests(NNTPv1v2TestsMixin, MockedNNTPTestsMixin, unittest.TestCase): | |
| 1184 | 1194 | """Tests an NNTP v1 server (no capabilities).""" | |
| Back | FazBrowse Home | New Git URL |
0 commit comments