diff --git a/django/utils/html.py b/django/utils/html.py
index a9ebd17..1f48ca7 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -18,7 +18,7 @@ from django.utils.text import normalize_newlines
# Configuration for urlize() function.
TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)']
-WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>')]
+WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'), ('"', '"'), ("'", "'")]
# List of possible strings used for bullets in bulleted lists.
DOTS = ['·', '*', '\u2022', '', '•', '•']
@@ -205,7 +205,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
lead = lead + opening
# Keep parentheses at the end only if they're balanced.
if (middle.endswith(closing)
- and middle.count(closing) == middle.count(opening) + 1):
+ and (opening == closing or middle.count(closing) == middle.count(opening) + 1)):
middle = middle[:-len(closing)]
trail = closing + trail
diff --git a/tests/defaultfilters/tests.py b/tests/defaultfilters/tests.py
index 8596f8c..0092f4d 100644
--- a/tests/defaultfilters/tests.py
+++ b/tests/defaultfilters/tests.py
@@ -314,6 +314,11 @@ class DefaultFiltersTests(TestCase):
self.assertEqual(urlize('see test[at[example.com'),
'see test[at[example.com' )
+ # Check urlize handles quoted URLs properly (#17576)
+ self.assertEqual(urlize('{"link": "www.djangoproject.com"}'),
+ '{"link": "www.djangoproject.com"}')
+ self.assertEqual(urlize("{'link': 'www.djangoproject.com'}"),
+ "{'link': 'www.djangoproject.com'}")
def test_wordcount(self):
self.assertEqual(wordcount(''), 0)