FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-140691: urllib.request: Close FTP control socket if data socket can't connect by encukou · Pull Request #140835 · python/cpython · GitHub

Repository navigation

Next Next commit
Add a test for unclosed socket
  • Loading branch information
encukou committed Oct 31, 2025
commit bd3c98d44ce1da5f5edfe9120eec0c0ce4262c43
51 changes: 41 additions & 10 deletions Lib/test/test_urllib2net.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import contextlib
import errno
import unittest
from unittest import mock
Comment thread
cmaloney marked this conversation as resolved.
Resolved
from test import support
from test.support import os_helper
from test.support import socket_helper
from test.support import ResourceDenied
from test.support.warnings_helper import check_no_resource_warning

import os
import socket
Expand Down Expand Up @@ -143,6 +146,36 @@ def test_ftp(self):
]
self._test_urls(urls, self._extra_handlers())

def test_ftp_no_leak(self):
# gh-140691: When the data connection (but not control connection)
# cannot be made established, we shouldn't leave an open socket object.

class MockError(OSError):
Comment thread
cmaloney marked this conversation as resolved.
Resolved
pass

orig_create_connection = socket.create_connection
def patched_create_connection(address, *args, **kwargs):
"""Simulate REJECTing connections to ports other than 21"""
host, port = address
if port != 21:
raise MockError()
return orig_create_connection(address, *args, **kwargs)

url = 'ftp://www.pythontest.net/README'
entry = url, None, urllib.error.URLError
no_cache_handlers = [urllib.request.FTPHandler()]
cache_handlers = self._extra_handlers()
with mock.patch('socket.create_connection', patched_create_connection):
with check_no_resource_warning(self):
# Try without CacheFTPHandler
self._test_urls([entry], handlers=no_cache_handlers)
with check_no_resource_warning(self):
# Try with CacheFTPHandler (uncached & cached)
self._test_urls([entry, entry], cache_handlers)
# Try without the mock: the handler should not use a closed connection
with check_no_resource_warning(self):
self._test_urls([url], cache_handlers)

def test_file(self):
TESTFN = os_helper.TESTFN
f = open(TESTFN, 'w')
Expand Down Expand Up @@ -234,18 +267,16 @@ def _test_urls(self, urls, handlers, retry=True):
else:
req = expected_err = None

if expected_err:
context = self.assertRaises(expected_err)
else:
context = contextlib.nullcontext()

with socket_helper.transient_internet(url):
try:
f = None
with context:
f = urlopen(url, req, support.INTERNET_TIMEOUT)
# urllib.error.URLError is a subclass of OSError
except OSError as err:
if expected_err:
msg = ("Didn't get expected error(s) %s for %s %s, got %s: %s" %
(expected_err, url, req, type(err), err))
self.assertIsInstance(err, expected_err, msg)
else:
raise
Comment on lines -242 to -247

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

(The new context (assertRaises) handles the expected error; printing information is nowadays covered by subTest)

else:
if f is not None:
try:
with time_out, \
socket_peer_reset, \
Expand Down

Back | FazBrowse Home | New Git URL