Handle redirect HTTP responses, in particular if a Bugzilla server
is redirecting from http to https.

We try to detect "Bugzilla URL base is over here" when we ask for
show_bug.cgi and remember that for future requests to the same BugServer
to avoid too many redirections.

Switch from caching connection on the BugServer to a global connection
cache, and rewrite the BugServer cache so to deal with the possibility
of redirections.
This commit is contained in:
Owen W. Taylor 2009-09-09 17:14:55 -04:00
Родитель 06187ce308
Коммит 2680c3c68d
2 изменённых файлов: 89 добавлений и 25 удалений

4
TODO
Просмотреть файл

@ -31,10 +31,6 @@ Automatically guess obvious obsoletes
matches the Subject of the attachment, start the Obsoletes line
uncommented?
Handle redirects:
Should follow redirects, both to different URLs and http => https
Better display of errors
The switch to XML-RPC greatly improves errors when filing a new bug,

110
git-bz
Просмотреть файл

@ -76,7 +76,7 @@ import tempfile
import time
import traceback
import xmlrpclib
import urllib
import urlparse
from xml.etree.cElementTree import ElementTree
# Globals
@ -697,6 +697,20 @@ class BugPatch(object):
class NoXmlRpcError(Exception):
pass
connections = {}
def get_connection(host, https):
identifier = (host, https)
if not identifier in connections:
if https:
connection = httplib.HTTPSConnection(host, 443)
else:
connection = httplib.HTTPConnection(host, 80)
connections[identifier] = connection
return connections[identifier]
class BugServer(object):
def __init__(self, host, https):
self.host = host
@ -704,18 +718,8 @@ class BugServer(object):
self.cookies = get_bugzilla_cookies(host)
self._connection = None
self._xmlrpc_proxy = None
def get_connection(self):
if self._connection is None:
if self.https:
self._connection = httplib.HTTPSConnection(self.host, 443)
else:
self._connection = httplib.HTTPConnection(self.host, 80)
return self._connection
def get_cookie_string(self):
return ("Bugzilla_login=%s; Bugzilla_logincookie=%s" %
(self.cookies['Bugzilla_login'], self.cookies['Bugzilla_logincookie']))
@ -725,8 +729,71 @@ class BugServer(object):
headers['Cookie'] = self.get_cookie_string()
headers['User-Agent'] = "git-bz"
self.get_connection().request(method, url, data, headers)
return self.get_connection().getresponse()
seen_urls = []
connection = get_connection(self.host, self.https)
while True:
connection.request(method, url, data, headers)
response = connection.getresponse()
seen_urls.append(url)
# Redirect status codes:
#
# 301 (Moved Permanently): Redo with the new URL,
# save the new location.
# 303 (See Other): Redo with the method changed to GET/HEAD.
# 307 (Temporary Redirect): Redo with the new URL, don't
# save the new location.
#
# [ For 301/307, you are supposed to ask the user if the
# method isn't GET/HEAD, but we're automating anyways... ]
#
# 302 (Found): The confusing one, and the one that
# Bugzilla uses, both to redirect to http to https and to
# redirect attachment.cgi&action=view to a different base URL
# for security. Specified like 307, traditionally treated as 301.
#
# See http://en.wikipedia.org/wiki/HTTP_302
if response.status in (301, 302, 303, 307):
new_url = response.getheader("location")
if new_url is None:
die("Redirect received without a location to redirect to")
if new_url in seen_urls or len(seen_urls) >= 10:
die("Circular redirect or too many redirects")
old_split = urlparse.urlsplit(url)
new_split = urlparse.urlsplit(new_url)
new_https = new_split.scheme == 'https'
if new_split.hostname != self.host or new_https != self.https:
connection = get_connection(new_split.hostname, new_https != self.https)
# This is a bit of a hack to avoid keeping on redirecting for every
# request. If the server redirected show_bug.cgi we assume it's
# really saying "hey, the bugzilla instance is really over here".
#
# We can't do this for old.split.path == new_split.path because of
# attachment.cgi, though we alternatively could just exclude
# attachment.cgi here.
if (response.status in (301, 302) and
method == 'GET' and
old_split.path == '/show_bug.cgi' and new_split.path == '/show_bug.cgi'):
self.host = new_split.hostname
self.https = new_https
# We can't treat 302 like 303 because of the use of 302 for http
# to https, though the hack above will hopefully get us on https
# before we try to POST.
if response.status == 303:
if method not in ('GET', 'HEAD'):
method = 'GET'
# Get the relative component of the new URL
url = urlparse.urlunsplit((None, None, new_split.path, new_split.query, new_split.fragment))
else:
return response
def send_post(self, url, fields, files=None):
content_type, body = encode_multipart_formdata(fields, files)
@ -784,17 +851,18 @@ class BugTransport(xmlrpclib.Transport):
xmlrpclib.Transport.send_request(self, connection, *args)
connection.putheader("Cookie", self.server.get_cookie_string())
servers = []
servers = {}
# Note that if we detect that we are redirecting, we may rewrite the
# host/https of the server to avoid doing too many redirections, and
# so the host,https we connect to may be different than what we use
# to look up the server.
def get_bug_server(host, https):
for server in servers:
if server.host == host and server.https == https:
return server
identifier = (host, https)
if not identifier in servers:
servers[identifier] = BugServer(host, https)
server = BugServer(host, https)
servers.append(server)
return server
return servers[identifier]
# Unfortunately, Bugzilla doesn't set a useful status code for
# form posts. Because it's very confusing to claim we succeeded