Skip to content

Commit b89b5df

Browse files
committed
merge with 3.3
2 parents 68457be + 045ee06 commit b89b5df

File tree

13 files changed

+191
-38
lines changed

13 files changed

+191
-38
lines changed

Doc/library/http.client.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ The following exceptions are raised as appropriate:
169169
A subclass of :exc:`HTTPException`. Raised if a server responds with a HTTP
170170
status code that we don't understand.
171171

172-
The constants defined in this module are:
173172

173+
The constants defined in this module are:
174174

175175
.. data:: HTTP_PORT
176176

Doc/library/ssl.rst

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,10 @@ Certificate handling
286286
Verify that *cert* (in decoded format as returned by
287287
:meth:`SSLSocket.getpeercert`) matches the given *hostname*. The rules
288288
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.
293293

294294
:exc:`CertificateError` is raised on failure. On success, the function
295295
returns nothing::
@@ -304,6 +304,13 @@ Certificate handling
304304

305305
.. versionadded:: 3.2
306306

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+
307314
.. function:: cert_time_to_seconds(timestring)
308315

309316
Returns a floating-point value containing a normal seconds-after-the-epoch

Lib/http/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@
214214

215215
# maximal line length when calling readline().
216216
_MAXLINE = 65536
217+
_MAXHEADERS = 100
218+
217219

218220
class HTTPMessage(email.message.Message):
219221
# XXX The only usage of this method is in
@@ -261,6 +263,8 @@ def parse_headers(fp, _class=HTTPMessage):
261263
if len(line) > _MAXLINE:
262264
raise LineTooLong("header line")
263265
headers.append(line)
266+
if len(headers) > _MAXHEADERS:
267+
raise HTTPException("got more than %d headers" % _MAXHEADERS)
264268
if line in (b'\r\n', b'\n', b''):
265269
break
266270
hstring = b''.join(headers).decode('iso-8859-1')

Lib/imaplib.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@
4343
IMAP4_SSL_PORT = 993
4444
AllowedVersions = ('IMAP4REV1', 'IMAP4') # Most recent first
4545

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+
4655
# Commands
4756

4857
Commands = {
@@ -256,7 +265,10 @@ def read(self, size):
256265

257266
def readline(self):
258267
"""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
260272

261273

262274
def send(self, data):

Lib/nntplib.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@
8585
"decode_header",
8686
]
8787

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+
8895
# Exceptions raised when an error or invalid response is received
8996
class NNTPError(Exception):
9097
"""Base class for all nntplib exceptions"""
@@ -424,7 +431,9 @@ def _getline(self, strip_crlf=True):
424431
"""Internal: return one line from the server, stripping _CRLF.
425432
Raise EOFError if the connection is closed.
426433
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')
428437
if self.debugging > 1:
429438
print('*get*', repr(line))
430439
if not line: raise EOFError

Lib/poplib.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ class error_proto(Exception): pass
4040
LF = b'\n'
4141
CRLF = CR+LF
4242

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+
4349

4450
class POP3:
4551

@@ -118,7 +124,10 @@ def _putcmd(self, line):
118124
# Raise error_proto('-ERR EOF') if the connection is closed.
119125

120126
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+
122131
if self._debugging > 1: print('*get*', repr(line))
123132
if not line: raise error_proto('-ERR EOF')
124133
octets = len(line)

Lib/ssl.py

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -166,31 +166,59 @@ class CertificateError(ValueError):
166166
pass
167167

168168

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+
"""
170174
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)
188216

189217

190218
def match_hostname(cert, hostname):
191219
"""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*.
194222
195223
CertificateError is raised on failure. On success, the function
196224
returns nothing.
@@ -201,7 +229,7 @@ def match_hostname(cert, hostname):
201229
san = cert.get('subjectAltName', ())
202230
for key, value in san:
203231
if key == 'DNS':
204-
if _dnsname_to_pat(value).match(hostname):
232+
if _dnsname_match(value, hostname):
205233
return
206234
dnsnames.append(value)
207235
if not dnsnames:
@@ -212,7 +240,7 @@ def match_hostname(cert, hostname):
212240
# XXX according to RFC 2818, the most specific Common Name
213241
# must be used.
214242
if key == 'commonName':
215-
if _dnsname_to_pat(value).match(hostname):
243+
if _dnsname_match(value, hostname):
216244
return
217245
dnsnames.append(value)
218246
if len(dnsnames) > 1:

Lib/test/test_httplib.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,15 @@ def test_readinto_head(self):
347347
self.fail("Did not expect response from HEAD request")
348348
self.assertEqual(bytes(b), b'\x00'*5)
349349

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+
350359
def test_send_file(self):
351360
expected = (b'GET /foo HTTP/1.1\r\nHost: example.com\r\n'
352361
b'Accept-Encoding: identity\r\nContent-Length:')

Lib/test/test_imaplib.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,17 @@ def cmd_AUTHENTICATE(self, tag, args):
325325
self.assertEqual(ret, "OK")
326326

327327

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+
328339
class ThreadedNetworkedTests(BaseThreadedNetworkedTests):
329340

330341
server_class = socketserver.TCPServer

Lib/test/test_nntplib.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,11 @@ def handle_NEWNEWS(self, group, date_str, time_str):
584584
<a4929a40-6328-491a-aaaf-cb79ed7309a2@q2g2000vbk.googlegroups.com>
585585
<f30c0419-f549-4218-848f-d7d0131da931@y3g2000vbm.googlegroups.com>
586586
.""")
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.')
587592
else:
588593
self.push_lit("""\
589594
230 An empty list of newsarticles follows
@@ -1179,6 +1184,11 @@ def test_ihave(self):
11791184
self.assertEqual(cm.exception.response,
11801185
"435 Article not wanted")
11811186

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+
11821192

11831193
class NNTPv1Tests(NNTPv1v2TestsMixin, MockedNNTPTestsMixin, unittest.TestCase):
11841194
"""Tests an NNTP v1 server (no capabilities)."""

0 commit comments

Comments
 (0)