70
class _BufferedMakefileSocket(object):
72
def __init__(self, sock):
78
class _ReportingFileSocket(object):
80
def __init__(self, filesock, report_activity=None):
81
self.filesock = filesock
82
self._report_activity = report_activity
84
def report_activity(self, size, direction):
85
if self._report_activity:
86
self._report_activity(size, direction)
88
def read(self, size=1):
89
s = self.filesock.read(size)
90
self.report_activity(len(s), 'read')
94
# This should be readline(self, size=-1), but httplib in python 2.4 and
95
# 2.5 defines a SSLFile wrapper whose readline method lacks the size
96
# parameter. So until we drop support for 2.4 and 2.5 and since we
97
# don't *need* the size parameter we'll stay with readline(self)
99
s = self.filesock.readline()
100
self.report_activity(len(s), 'read')
103
def __getattr__(self, name):
104
return getattr(self.filesock, name)
107
class _ReportingSocket(object):
109
def __init__(self, sock, report_activity=None):
111
self._report_activity = report_activity
113
def report_activity(self, size, direction):
114
if self._report_activity:
115
self._report_activity(size, direction)
117
def sendall(self, s, *args):
118
self.sock.sendall(s, *args)
119
self.report_activity(len(s), 'write')
121
def recv(self, *args):
122
s = self.sock.recv(*args)
123
self.report_activity(len(s), 'read')
75
126
def makefile(self, mode='r', bufsize=-1):
76
return self.sock.makefile(mode, 65536)
127
# httplib creates a fileobject that doesn't do buffering, which
128
# makes fp.readline() very expensive because it only reads one byte
129
# at a time. So we wrap the socket in an object that forces
130
# sock.makefile to make a buffered file.
131
fsock = self.sock.makefile(mode, 65536)
132
# And wrap that into a reporting kind of fileobject
133
return _ReportingFileSocket(fsock, self._report_activity)
78
135
def __getattr__(self, name):
79
136
return getattr(self.sock, name)
96
153
# 8k chunks should be fine.
97
154
_discarded_buf_size = 8192
99
def __init__(self, sock, *args, **kwargs):
100
# httplib creates a fileobject that doesn't do buffering, which
101
# makes fp.readline() very expensive because it only reads one byte
102
# at a time. So we wrap the socket in an object that forces
103
# sock.makefile to make a buffered file.
104
sock = _BufferedMakefileSocket(sock)
105
httplib.HTTPResponse.__init__(self, sock, *args, **kwargs)
108
157
"""Begin to read the response from the server.
211
261
# Preserve our preciousss
214
# Let httplib.HTTPConnection do its housekeeping
264
# Let httplib.HTTPConnection do its housekeeping
216
266
# Restore our preciousss
269
def _wrap_socket_for_reporting(self, sock):
270
"""Wrap the socket before anybody use it."""
271
self.sock = _ReportingSocket(sock, self._report_activity)
220
274
class HTTPConnection(AbstractHTTPConnection, httplib.HTTPConnection):
222
276
# XXX: Needs refactoring at the caller level.
223
def __init__(self, host, port=None, proxied_host=None):
224
AbstractHTTPConnection.__init__(self)
277
def __init__(self, host, port=None, proxied_host=None,
278
report_activity=None):
279
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
225
280
# Use strict=True since we don't support HTTP/0.9
226
281
httplib.HTTPConnection.__init__(self, host, port, strict=True)
227
282
self.proxied_host = proxied_host
230
285
if 'http' in debug.debug_flags:
231
286
self._mutter_connect()
232
287
httplib.HTTPConnection.connect(self)
235
# FIXME: Should test for ssl availability
288
self._wrap_socket_for_reporting(self.sock)
291
# Build the appropriate socket wrapper for ssl
293
# python 2.6 introduced a better ssl package
295
_ssl_wrap_socket = ssl.wrap_socket
297
# python versions prior to 2.6 don't have ssl and ssl.wrap_socket instead
298
# they use httplib.FakeSocket
299
def _ssl_wrap_socket(sock, key_file, cert_file):
300
ssl_sock = socket.ssl(sock, key_file, cert_file)
301
return httplib.FakeSocket(sock, ssl_sock)
236
304
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
238
306
def __init__(self, host, port=None, key_file=None, cert_file=None,
240
AbstractHTTPConnection.__init__(self)
308
report_activity=None):
309
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
241
310
# Use strict=True since we don't support HTTP/0.9
242
311
httplib.HTTPSConnection.__init__(self, host, port,
243
312
key_file, cert_file, strict=True)
247
316
if 'http' in debug.debug_flags:
248
317
self._mutter_connect()
249
318
httplib.HTTPConnection.connect(self)
319
self._wrap_socket_for_reporting(self.sock)
250
320
if self.proxied_host is None:
251
321
self.connect_to_origin()
253
323
def connect_to_origin(self):
254
ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
255
self.sock = httplib.FakeSocket(self.sock, ssl)
324
ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
325
# Wrap the ssl socket before anybody use it
326
self._wrap_socket_for_reporting(ssl_sock)
258
329
class Request(urllib2.Request):
297
368
def set_proxy(self, proxy, type):
298
369
"""Set the proxy and remember the proxied host."""
299
self.proxied_host = self.get_host()
370
host, port = urllib.splitport(self.get_host())
372
# We need to set the default port ourselves way before it gets set
373
# in the HTTP[S]Connection object at build time.
374
if self.type == 'https':
375
conn_class = HTTPSConnection
377
conn_class = HTTPConnection
378
port = conn_class.default_port
379
self.proxied_host = '%s:%s' % (host, port)
300
380
urllib2.Request.set_proxy(self, proxy, type)
923
1019
# in such a cycle by default.
924
1020
self._retry_count = None
1022
def _parse_auth_header(self, server_header):
1023
"""Parse the authentication header.
1025
:param server_header: The value of the header sent by the server
1026
describing the authenticaion request.
1028
:return: A tuple (scheme, remainder) scheme being the first word in the
1029
given header (lower cased), remainder may be None.
1032
scheme, remainder = server_header.split(None, 1)
1034
scheme = server_header
1036
return (scheme.lower(), remainder)
926
1038
def update_auth(self, auth, key, value):
927
1039
"""Update a value in auth marking the auth as modified if needed"""
928
1040
old_value = auth.get(key, None)
947
1059
# Let's be ready for next round
948
1060
self._retry_count = None
950
server_header = headers.get(self.auth_required_header, None)
951
if server_header is None:
1062
server_headers = headers.getheaders(self.auth_required_header)
1063
if not server_headers:
952
1064
# The http error MUST have the associated
953
1065
# header. This must never happen in production code.
954
1066
raise KeyError('%s not found' % self.auth_required_header)
956
1068
auth = self.get_auth(request)
957
if auth.get('user', None) is None:
958
# Without a known user, we can't authenticate
961
1069
auth['modified'] = False
962
if self.auth_match(server_header, auth):
963
# auth_match may have modified auth (by adding the
964
# password or changing the realm, for example)
965
if (request.get_header(self.auth_header, None) is not None
966
and not auth['modified']):
967
# We already tried that, give up
971
request.connection.cleanup_pipe()
972
response = self.parent.open(request)
974
self.auth_successful(request, response)
1070
# Put some common info in auth if the caller didn't
1071
if auth.get('path', None) is None:
1073
host, port, path) = urlutils.parse_url(request.get_full_url())
1074
self.update_auth(auth, 'protocol', protocol)
1075
self.update_auth(auth, 'host', host)
1076
self.update_auth(auth, 'port', port)
1077
self.update_auth(auth, 'path', path)
1078
# FIXME: the auth handler should be selected at a single place instead
1079
# of letting all handlers try to match all headers, but the current
1080
# design doesn't allow a simple implementation.
1081
for server_header in server_headers:
1082
# Several schemes can be proposed by the server, try to match each
1084
matching_handler = self.auth_match(server_header, auth)
1085
if matching_handler:
1086
# auth_match may have modified auth (by adding the
1087
# password or changing the realm, for example)
1088
if (request.get_header(self.auth_header, None) is not None
1089
and not auth['modified']):
1090
# We already tried that, give up
1093
# Only the most secure scheme proposed by the server should be
1094
# used, since the handlers use 'handler_order' to describe that
1095
# property, the first handler tried takes precedence, the
1096
# others should not attempt to authenticate if the best one
1098
best_scheme = auth.get('best_scheme', None)
1099
if best_scheme is None:
1100
# At that point, if current handler should doesn't succeed
1101
# the credentials are wrong (or incomplete), but we know
1102
# that the associated scheme should be used.
1103
best_scheme = auth['best_scheme'] = self.scheme
1104
if best_scheme != self.scheme:
1107
if self.requires_username and auth.get('user', None) is None:
1108
# Without a known user, we can't authenticate
1112
request.connection.cleanup_pipe()
1113
# Retry the request with an authentication header added
1114
response = self.parent.open(request)
1116
self.auth_successful(request, response)
976
1118
# We are not qualified to handle the authentication.
977
1119
# Note: the authentication error handling will try all
978
1120
# available handlers. If one of them authenticates
1028
1170
self._retry_count = None
1030
1172
def get_user_password(self, auth):
1031
"""Ask user for a password if none is already available."""
1173
"""Ask user for a password if none is already available.
1175
:param auth: authentication info gathered so far (from the initial url
1176
and then during dialog with the server).
1032
1178
auth_conf = config.AuthenticationConfig()
1034
password = auth['password']
1179
user = auth.get('user', None)
1180
password = auth.get('password', None)
1035
1181
realm = auth['realm']
1037
1183
if user is None:
1038
user = auth.get_user(auth['protocol'], auth['host'],
1039
port=auth['port'], path=auth['path'],
1042
# Default to local user
1043
user = getpass.getuser()
1045
if password is None:
1184
user = auth_conf.get_user(auth['protocol'], auth['host'],
1185
port=auth['port'], path=auth['path'],
1186
realm=realm, ask=True,
1187
prompt=self.build_username_prompt(auth))
1188
if user is not None and password is None:
1046
1189
password = auth_conf.get_password(
1047
1190
auth['protocol'], auth['host'], user, port=auth['port'],
1048
1191
path=auth['path'], realm=realm,
1068
1211
prompt += ' password'
1214
def _build_username_prompt(self, auth):
1215
"""Build a prompt taking the protocol used into account.
1217
The AuthHandler is used by http and https, we want that information in
1218
the prompt, so we build the prompt from the authentication dict which
1219
contains all the needed parts.
1221
Also, http and proxy AuthHandlers present different prompts to the
1222
user. The daughter classes should implements a public
1223
build_username_prompt using this method.
1225
prompt = '%s' % auth['protocol'].upper() + ' %(host)s'
1226
realm = auth['realm']
1227
if realm is not None:
1228
prompt += ", Realm: '%s'" % realm
1229
prompt += ' username'
1071
1232
def http_request(self, request):
1072
1233
"""Insert an authentication header if information is available"""
1073
1234
auth = self.get_auth(request)
1078
1239
https_request = http_request # FIXME: Need test
1242
class NegotiateAuthHandler(AbstractAuthHandler):
1243
"""A authentication handler that handles WWW-Authenticate: Negotiate.
1245
At the moment this handler supports just Kerberos. In the future,
1246
NTLM support may also be added.
1249
scheme = 'negotiate'
1251
requires_username = False
1253
def auth_match(self, header, auth):
1254
scheme, raw_auth = self._parse_auth_header(header)
1255
if scheme != self.scheme:
1257
self.update_auth(auth, 'scheme', scheme)
1258
resp = self._auth_match_kerberos(auth)
1261
# Optionally should try to authenticate using NTLM here
1262
self.update_auth(auth, 'negotiate_response', resp)
1265
def _auth_match_kerberos(self, auth):
1266
"""Try to create a GSSAPI response for authenticating against a host."""
1267
if not have_kerberos:
1269
ret, vc = kerberos.authGSSClientInit("HTTP@%(host)s" % auth)
1271
trace.warning('Unable to create GSSAPI context for %s: %d',
1274
ret = kerberos.authGSSClientStep(vc, "")
1276
trace.mutter('authGSSClientStep failed: %d', ret)
1278
return kerberos.authGSSClientResponse(vc)
1280
def build_auth_header(self, auth, request):
1281
return "Negotiate %s" % auth['negotiate_response']
1283
def auth_params_reusable(self, auth):
1284
# If the auth scheme is known, it means a previous
1285
# authentication was successful, all information is
1286
# available, no further checks are needed.
1287
return (auth.get('scheme', None) == 'negotiate' and
1288
auth.get('negotiate_response', None) is not None)
1081
1291
class BasicAuthHandler(AbstractAuthHandler):
1082
1292
"""A custom basic authentication handler."""
1084
1295
handler_order = 500
1086
1296
auth_regexp = re.compile('realm="([^"]*)"', re.I)
1088
1298
def build_auth_header(self, auth, request):
1090
1300
auth_header = 'Basic ' + raw.encode('base64').strip()
1091
1301
return auth_header
1303
def extract_realm(self, header_value):
1304
match = self.auth_regexp.search(header_value)
1307
realm = match.group(1)
1093
1310
def auth_match(self, header, auth):
1094
scheme, raw_auth = header.split(None, 1)
1095
scheme = scheme.lower()
1096
if scheme != 'basic':
1311
scheme, raw_auth = self._parse_auth_header(header)
1312
if scheme != self.scheme:
1099
match = self.auth_regexp.search(raw_auth)
1315
match, realm = self.extract_realm(raw_auth)
1101
realm = match.groups()
1102
if scheme != 'basic':
1105
1317
# Put useful info into auth
1106
1318
self.update_auth(auth, 'scheme', scheme)
1107
1319
self.update_auth(auth, 'realm', realm)
1108
if auth['user'] is None or auth['password'] is None:
1320
if (auth.get('user', None) is None
1321
or auth.get('password', None) is None):
1109
1322
user, password = self.get_user_password(auth)
1110
1323
self.update_auth(auth, 'user', user)
1111
1324
self.update_auth(auth, 'password', password)
1170
1383
# Put useful info into auth
1171
1384
self.update_auth(auth, 'scheme', scheme)
1172
1385
self.update_auth(auth, 'realm', realm)
1173
if auth['user'] is None or auth['password'] is None:
1386
if auth.get('user', None) is None or auth.get('password', None) is None:
1174
1387
user, password = self.get_user_password(auth)
1175
1388
self.update_auth(auth, 'user', user)
1176
1389
self.update_auth(auth, 'password', password)
1359
1588
def __init__(self,
1360
1589
connection=ConnectionHandler,
1361
1590
redirect=HTTPRedirectHandler,
1362
error=HTTPErrorProcessor,):
1363
self._opener = urllib2.build_opener( \
1364
connection, redirect, error,
1591
error=HTTPErrorProcessor,
1592
report_activity=None):
1593
self._opener = urllib2.build_opener(
1594
connection(report_activity=report_activity),
1365
1596
ProxyHandler(),
1366
1597
HTTPBasicAuthHandler(),
1367
1598
HTTPDigestAuthHandler(),
1599
HTTPNegotiateAuthHandler(),
1368
1600
ProxyBasicAuthHandler(),
1369
1601
ProxyDigestAuthHandler(),
1602
ProxyNegotiateAuthHandler(),
1372
1605
HTTPDefaultErrorHandler,