Auth: Allow "@" in login username for feeds (bug 1521)

Thanks to Eric Smith for reporting this issue and
providing a login to help test and debug this bug.
This commit is contained in:
Thomas Perl 2012-01-09 14:41:03 +01:00
parent 2417503478
commit 0df167ff3e
3 changed files with 41 additions and 23 deletions

View File

@ -2142,8 +2142,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.show_message(message, title, widget=self.treeChannels)
# Report subscriptions that require authentication
retry_podcasts = {}
if authreq:
retry_podcasts = {}
for url in authreq:
title = _('Podcast requires authentication')
message = _('Please login to %s:') % (cgi.escape(url),)
@ -2158,10 +2158,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
failed.append(url)
break
# If we have authentication data to retry, do so here
if retry_podcasts:
self.add_podcast_list(retry_podcasts.keys(), retry_podcasts)
# Report website redirections
for url in redirections:
title = _('Website redirection detected')
@ -2196,6 +2192,14 @@ class gPodder(BuilderWidget, dbus.service.Object):
# Update the list of subscribed podcasts
self.update_podcast_list_model(select_url=url)
# If we have authentication data to retry, do so here
if retry_podcasts:
self.add_podcast_list(retry_podcasts.keys(), retry_podcasts)
# This will NOT show new episodes for podcasts that have
# been added ("worked"), but it will prevent problems with
# multiple dialogs being open at the same time ;)
return
# Offer to download new episodes
episodes = []
for podcast in self.channels:

View File

@ -131,7 +131,9 @@ class CoverDownloader(ObservableService):
if not os.path.exists(channel.cover_file):
if url is None:
url = channel.cover_url
# We have to use authenticate_url, because password-protected
# feeds might keep their cover art also protected (bug 1521)
url = channel.authenticate_url(channel.cover_url)
new_url = youtube.get_real_cover(channel.url)
if new_url is not None:

View File

@ -237,19 +237,17 @@ def username_password_from_url(url):
...
ValueError: URL has to be a string or unicode object.
>>> username_password_from_url('http://a@b:c@host.com/')
Traceback (most recent call last):
...
ValueError: "@" must be encoded for username/password (RFC1738).
('a@b', 'c')
>>> username_password_from_url('ftp://a:b:c@host.com/')
Traceback (most recent call last):
...
ValueError: ":" must be encoded for username/password (RFC1738).
('a', 'b:c')
>>> username_password_from_url('http://i%2Fo:P%40ss%3A@host.com/')
('i/o', 'P@ss:')
>>> username_password_from_url('ftp://%C3%B6sterreich@host.com/')
('\xc3\xb6sterreich', None)
>>> username_password_from_url('http://w%20x:y%20z@example.org/')
('w x', 'y z')
>>> username_password_from_url('http://example.com/x@y:z@test.com/')
(None, None)
"""
if type(url) not in (str, unicode):
raise ValueError('URL has to be a string or unicode object.')
@ -262,11 +260,20 @@ def username_password_from_url(url):
(authentication, netloc) = netloc.rsplit('@', 1)
if ':' in authentication:
(username, password) = authentication.split(':', 1)
# RFC1738 dictates that we should not allow these unquoted
# characters in the username and password field (Section 3.1).
for c in (':', '@', '/'):
if c in username or c in password:
raise ValueError('"%c" must be encoded for username/password (RFC1738).' % c)
# RFC1738 dictates that we should not allow ['/', '@', ':']
# characters in the username and password field (Section 3.1):
#
# 1. The "/" can't be in there at this point because of the way
# urlparse (which we use above) works.
# 2. Due to gPodder bug 1521, we allow "@" in the username and
# password field. We use netloc.rsplit('@', 1), which will
# make sure that we split it at the last '@' in netloc.
# 3. The colon must be excluded (RFC2617, Section 2) in the
# username, but is apparently allowed in the password. This
# is handled by the authentication.split(':', 1) above, and
# will cause any extraneous ':'s to be part of the password.
username = urllib.unquote(username)
password = urllib.unquote(password)
else:
@ -829,13 +836,15 @@ def url_strip_authentication(url):
'http://x.org/'
>>> url_strip_authentication('http://P%40%3A:i%2F@cx.lan')
'http://cx.lan'
>>> url_strip_authentication('http://x@x.com:s3cret@example.com/')
'http://example.com/'
"""
url_parts = list(urlparse.urlsplit(url))
# url_parts[1] is the HOST part of the URL
# Remove existing authentication data
if '@' in url_parts[1]:
url_parts[1] = url_parts[1].split('@', 2)[1]
url_parts[1] = url_parts[1].rsplit('@', 1)[1]
return urlparse.urlunsplit(url_parts)
@ -858,21 +867,24 @@ def url_add_authentication(url, username, password):
>>> url_add_authentication('http://localhost/x', 'aa', 'bc')
'http://aa:bc@localhost/x'
>>> url_add_authentication('http://blubb.lan/u.html', 'i/o', 'P@ss:')
'http://i%2Fo:P%40ss%3A@blubb.lan/u.html'
'http://i%2Fo:P@ss:@blubb.lan/u.html'
>>> url_add_authentication('http://a:b@x.org/', 'c', 'd')
'http://c:d@x.org/'
>>> url_add_authentication('http://i%2F:P%40%3A@cx.lan', 'P@:', 'i/')
'http://P%40%3A:i%2F@cx.lan'
>>> url_add_authentication('http://i%2F:P%40%3A@cx.lan', 'P@x', 'i/')
'http://P@x:i%2F@cx.lan'
>>> url_add_authentication('http://x.org/', 'a b', 'c d')
'http://a%20b:c%20d@x.org/'
"""
if username is None or username == '':
return url
username = urllib.quote(username, safe='')
# Relaxations of the strict quoting rules (bug 1521):
# 1. Accept '@' in username and password
# 2. Acecpt ':' in password only
username = urllib.quote(username, safe='@')
if password is not None:
password = urllib.quote(password, safe='')
password = urllib.quote(password, safe='@:')
auth_string = ':'.join((username, password))
else:
auth_string = username