~soren/nova/iptables-security-groups

« back to all changes in this revision

Viewing changes to vendor/boto/boto/connection.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
 
2
# Copyright (c) 2008 rPath, Inc.
 
3
# Copyright (c) 2009 The Echo Nest Corporation
 
4
#
 
5
# Permission is hereby granted, free of charge, to any person obtaining a
 
6
# copy of this software and associated documentation files (the
 
7
# "Software"), to deal in the Software without restriction, including
 
8
# without limitation the rights to use, copy, modify, merge, publish, dis-
 
9
# tribute, sublicense, and/or sell copies of the Software, and to permit
 
10
# persons to whom the Software is furnished to do so, subject to the fol-
 
11
# lowing conditions:
 
12
#
 
13
# The above copyright notice and this permission notice shall be included
 
14
# in all copies or substantial portions of the Software.
 
15
#
 
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 
17
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
 
18
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
 
19
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 
20
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
21
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 
22
# IN THE SOFTWARE.
 
23
 
 
24
#
 
25
# Parts of this code were copied or derived from sample code supplied by AWS.
 
26
# The following notice applies to that code.
 
27
#
 
28
#  This software code is made available "AS IS" without warranties of any
 
29
#  kind.  You may copy, display, modify and redistribute the software
 
30
#  code either by itself or as incorporated into your code; provided that
 
31
#  you do not remove any proprietary notices.  Your use of this software
 
32
#  code is at your own risk and you waive any claim against Amazon
 
33
#  Digital Services, Inc. or its affiliates with respect to your use of
 
34
#  this software code. (c) 2006 Amazon Digital Services, Inc. or its
 
35
#  affiliates.
 
36
 
 
37
"""
 
38
Handles basic connections to AWS
 
39
"""
 
40
 
 
41
import base64
 
42
import hmac
 
43
import httplib
 
44
import socket, errno
 
45
import re
 
46
import sys
 
47
import time
 
48
import urllib, urlparse
 
49
import os
 
50
import xml.sax
 
51
import Queue
 
52
import boto
 
53
from boto.exception import BotoClientError, BotoServerError
 
54
from boto.resultset import ResultSet
 
55
import boto.utils
 
56
from boto import config, UserAgent, handler
 
57
 
 
58
#
 
59
# the following is necessary because of the incompatibilities
 
60
# between Python 2.4, 2.5, and 2.6 as well as the fact that some
 
61
# people running 2.4 have installed hashlib as a separate module
 
62
# this fix was provided by boto user mccormix.
 
63
# see: http://code.google.com/p/boto/issues/detail?id=172
 
64
# for more details.
 
65
#
 
66
try:
 
67
    from hashlib import sha1 as sha
 
68
    from hashlib import sha256 as sha256
 
69
 
 
70
    if sys.version[:3] == "2.4":
 
71
        # we are using an hmac that expects a .new() method.
 
72
        class Faker:
 
73
            def __init__(self, which):
 
74
                self.which = which
 
75
                self.digest_size = self.which().digest_size
 
76
 
 
77
            def new(self, *args, **kwargs):
 
78
                return self.which(*args, **kwargs)
 
79
 
 
80
        sha = Faker(sha)
 
81
        sha256 = Faker(sha256)
 
82
 
 
83
except ImportError:
 
84
    import sha
 
85
    sha256 = None
 
86
 
 
87
PORTS_BY_SECURITY = { True: 443, False: 80 }
 
88
 
 
89
class ConnectionPool:
 
90
    def __init__(self, hosts, connections_per_host):
 
91
        self._hosts = boto.utils.LRUCache(hosts)
 
92
        self.connections_per_host = connections_per_host
 
93
 
 
94
    def __getitem__(self, key):
 
95
        if key not in self._hosts:
 
96
            self._hosts[key] = Queue.Queue(self.connections_per_host)
 
97
        return self._hosts[key]
 
98
 
 
99
    def __repr__(self):
 
100
        return 'ConnectionPool:%s' % ','.join(self._hosts._dict.keys())
 
101
 
 
102
class AWSAuthConnection:
 
103
    def __init__(self, host, aws_access_key_id=None, aws_secret_access_key=None,
 
104
                 is_secure=True, port=None, proxy=None, proxy_port=None,
 
105
                 proxy_user=None, proxy_pass=None, debug=0,
 
106
                 https_connection_factory=None, path='/'):
 
107
        """
 
108
        :type host: string
 
109
        :param host: The host to make the connection to
 
110
 
 
111
        :type aws_access_key_id: string
 
112
        :param aws_access_key_id: AWS Access Key ID (provided by Amazon)
 
113
 
 
114
        :type aws_secret_access_key: string
 
115
        :param aws_secret_access_key: Secret Access Key (provided by Amazon)
 
116
 
 
117
        :type is_secure: boolean
 
118
        :param is_secure: Whether the connection is over SSL
 
119
 
 
120
        :type https_connection_factory: list or tuple
 
121
        :param https_connection_factory: A pair of an HTTP connection
 
122
                                         factory and the exceptions to catch.
 
123
                                         The factory should have a similar
 
124
                                         interface to L{httplib.HTTPSConnection}.
 
125
 
 
126
        :type proxy:
 
127
        :param proxy:
 
128
 
 
129
        :type proxy_port: int
 
130
        :param proxy_port: The port to use when connecting over a proxy
 
131
 
 
132
        :type proxy_user: string
 
133
        :param proxy_user: The username to connect with on the proxy
 
134
 
 
135
        :type proxy_pass: string
 
136
        :param proxy_pass: The password to use when connection over a proxy.
 
137
 
 
138
        :type port: integer
 
139
        :param port: The port to use to connect
 
140
        """
 
141
 
 
142
        self.num_retries = 5
 
143
        self.is_secure = is_secure
 
144
        self.handle_proxy(proxy, proxy_port, proxy_user, proxy_pass)
 
145
        # define exceptions from httplib that we want to catch and retry
 
146
        self.http_exceptions = (httplib.HTTPException, socket.error, socket.gaierror)
 
147
        # define values in socket exceptions we don't want to catch
 
148
        self.socket_exception_values = (errno.EINTR,)
 
149
        if https_connection_factory is not None:
 
150
            self.https_connection_factory = https_connection_factory[0]
 
151
            self.http_exceptions += https_connection_factory[1]
 
152
        else:
 
153
            self.https_connection_factory = None
 
154
        if (is_secure):
 
155
            self.protocol = 'https'
 
156
        else:
 
157
            self.protocol = 'http'
 
158
        self.host = host
 
159
        self.path = path
 
160
        if debug:
 
161
            self.debug = debug
 
162
        else:
 
163
            self.debug = config.getint('Boto', 'debug', debug)
 
164
        if port:
 
165
            self.port = port
 
166
        else:
 
167
            self.port = PORTS_BY_SECURITY[is_secure]
 
168
            
 
169
        if aws_access_key_id:
 
170
            self.aws_access_key_id = aws_access_key_id
 
171
        elif os.environ.has_key('AWS_ACCESS_KEY_ID'):
 
172
            self.aws_access_key_id = os.environ['AWS_ACCESS_KEY_ID']
 
173
        elif config.has_option('Credentials', 'aws_access_key_id'):
 
174
            self.aws_access_key_id = config.get('Credentials', 'aws_access_key_id')
 
175
 
 
176
        if aws_secret_access_key:
 
177
            self.aws_secret_access_key = aws_secret_access_key
 
178
        elif os.environ.has_key('AWS_SECRET_ACCESS_KEY'):
 
179
            self.aws_secret_access_key = os.environ['AWS_SECRET_ACCESS_KEY']
 
180
        elif config.has_option('Credentials', 'aws_secret_access_key'):
 
181
            self.aws_secret_access_key = config.get('Credentials', 'aws_secret_access_key')
 
182
 
 
183
        # initialize an HMAC for signatures, make copies with each request
 
184
        self.hmac = hmac.new(self.aws_secret_access_key, digestmod=sha)
 
185
        if sha256:
 
186
            self.hmac_256 = hmac.new(self.aws_secret_access_key, digestmod=sha256)
 
187
        else:
 
188
            self.hmac_256 = None
 
189
 
 
190
        # cache up to 20 connections per host, up to 20 hosts
 
191
        self._pool = ConnectionPool(20, 20)
 
192
        self._connection = (self.server_name(), self.is_secure)
 
193
        self._last_rs = None
 
194
 
 
195
    def __repr__(self):
 
196
        return '%s:%s' % (self.__class__.__name__, self.host)
 
197
 
 
198
    def _cached_name(self, host, is_secure):
 
199
        if host is None:
 
200
            host = self.server_name()
 
201
        cached_name = is_secure and 'https://' or 'http://'
 
202
        cached_name += host
 
203
        return cached_name
 
204
 
 
205
    def connection(self):
 
206
        return self.get_http_connection(*self._connection)
 
207
 
 
208
    connection = property(connection)
 
209
 
 
210
    def get_path(self, path='/'):
 
211
        pos = path.find('?')
 
212
        if pos >= 0:
 
213
            params = path[pos:]
 
214
            path = path[:pos]
 
215
        else:
 
216
            params = None
 
217
        if path[-1] == '/':
 
218
            need_trailing = True
 
219
        else:
 
220
            need_trailing = False
 
221
        path_elements = self.path.split('/')
 
222
        path_elements.extend(path.split('/'))
 
223
        path_elements = [p for p in path_elements if p]
 
224
        path = '/' + '/'.join(path_elements)
 
225
        if path[-1] != '/' and need_trailing:
 
226
            path += '/'
 
227
        if params:
 
228
            path = path + params
 
229
        return path
 
230
 
 
231
    def server_name(self, port=None):
 
232
        if not port:
 
233
            port = self.port
 
234
        if port == 80:
 
235
            signature_host = self.host
 
236
        else:
 
237
            # This unfortunate little hack can be attributed to
 
238
            # a difference in the 2.6 version of httplib.  In old
 
239
            # versions, it would append ":443" to the hostname sent
 
240
            # in the Host header and so we needed to make sure we
 
241
            # did the same when calculating the V2 signature.  In 2.6
 
242
            # it no longer does that.  Hence, this kludge.
 
243
            if sys.version[:3] == "2.6" and port == 443:
 
244
                signature_host = self.host
 
245
            else:
 
246
                signature_host = '%s:%d' % (self.host, port)
 
247
        return signature_host
 
248
 
 
249
    def handle_proxy(self, proxy, proxy_port, proxy_user, proxy_pass):
 
250
        self.proxy = proxy
 
251
        self.proxy_port = proxy_port
 
252
        self.proxy_user = proxy_user
 
253
        self.proxy_pass = proxy_pass
 
254
        if os.environ.has_key('http_proxy') and not self.proxy:
 
255
            pattern = re.compile(
 
256
                '(?:http://)?' \
 
257
                '(?:(?P<user>\w+):(?P<pass>.*)@)?' \
 
258
                '(?P<host>[\w\-\.]+)' \
 
259
                '(?::(?P<port>\d+))?'
 
260
            )
 
261
            match = pattern.match(os.environ['http_proxy'])
 
262
            if match:
 
263
                self.proxy = match.group('host')
 
264
                self.proxy_port = match.group('port')
 
265
                self.proxy_user = match.group('user')
 
266
                self.proxy_pass = match.group('pass')
 
267
        else:
 
268
            if not self.proxy:
 
269
                self.proxy = config.get_value('Boto', 'proxy', None)
 
270
            if not self.proxy_port:
 
271
                self.proxy_port = config.get_value('Boto', 'proxy_port', None)
 
272
            if not self.proxy_user:
 
273
                self.proxy_user = config.get_value('Boto', 'proxy_user', None)
 
274
            if not self.proxy_pass:
 
275
                self.proxy_pass = config.get_value('Boto', 'proxy_pass', None)
 
276
 
 
277
        if not self.proxy_port and self.proxy:
 
278
            print "http_proxy environment variable does not specify " \
 
279
                "a port, using default"
 
280
            self.proxy_port = self.port
 
281
        self.use_proxy = (self.proxy != None)
 
282
 
 
283
    def get_http_connection(self, host, is_secure):
 
284
        queue = self._pool[self._cached_name(host, is_secure)]
 
285
        try:
 
286
            return queue.get_nowait()
 
287
        except Queue.Empty:
 
288
            return self.new_http_connection(host, is_secure)
 
289
 
 
290
    def new_http_connection(self, host, is_secure):
 
291
        if self.use_proxy:
 
292
            host = '%s:%d' % (self.proxy, int(self.proxy_port))
 
293
        if host is None:
 
294
            host = self.server_name()
 
295
        boto.log.debug('establishing HTTP connection')
 
296
        if is_secure:
 
297
            if self.use_proxy:
 
298
                connection = self.proxy_ssl()
 
299
            elif self.https_connection_factory:
 
300
                connection = self.https_connection_factory(host)
 
301
            else:
 
302
                connection = httplib.HTTPSConnection(host)
 
303
        else:
 
304
            connection = httplib.HTTPConnection(host)
 
305
        if self.debug > 1:
 
306
            connection.set_debuglevel(self.debug)
 
307
        # self.connection must be maintained for backwards-compatibility
 
308
        # however, it must be dynamically pulled from the connection pool
 
309
        # set a private variable which will enable that
 
310
        if host.split(':')[0] == self.host and is_secure == self.is_secure:
 
311
            self._connection = (host, is_secure)
 
312
        return connection
 
313
 
 
314
    def put_http_connection(self, host, is_secure, connection):
 
315
        try:
 
316
            self._pool[self._cached_name(host, is_secure)].put_nowait(connection)
 
317
        except Queue.Full:
 
318
            # gracefully fail in case of pool overflow
 
319
            connection.close()
 
320
 
 
321
    def proxy_ssl(self):
 
322
        host = '%s:%d' % (self.host, self.port)
 
323
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
324
        try:
 
325
            sock.connect((self.proxy, int(self.proxy_port)))
 
326
        except:
 
327
            raise
 
328
        sock.sendall("CONNECT %s HTTP/1.0\r\n" % host)
 
329
        sock.sendall("User-Agent: %s\r\n" % UserAgent)
 
330
        if self.proxy_user and self.proxy_pass:
 
331
            for k, v in self.get_proxy_auth_header().items():
 
332
                sock.sendall("%s: %s\r\n" % (k, v))
 
333
        sock.sendall("\r\n")
 
334
        resp = httplib.HTTPResponse(sock, strict=True)
 
335
        resp.begin()
 
336
 
 
337
        if resp.status != 200:
 
338
            # Fake a socket error, use a code that make it obvious it hasn't
 
339
            # been generated by the socket library
 
340
            raise socket.error(-71,
 
341
                               "Error talking to HTTP proxy %s:%s: %s (%s)" %
 
342
                               (self.proxy, self.proxy_port, resp.status, resp.reason))
 
343
 
 
344
        # We can safely close the response, it duped the original socket
 
345
        resp.close()
 
346
 
 
347
        h = httplib.HTTPConnection(host)
 
348
        
 
349
        # Wrap the socket in an SSL socket
 
350
        if hasattr(httplib, 'ssl'):
 
351
            sslSock = httplib.ssl.SSLSocket(sock)
 
352
        else: # Old Python, no ssl module
 
353
            sslSock = socket.ssl(sock, None, None)
 
354
            sslSock = httplib.FakeSocket(sock, sslSock)
 
355
        # This is a bit unclean
 
356
        h.sock = sslSock
 
357
        return h
 
358
 
 
359
    def prefix_proxy_to_path(self, path, host=None):
 
360
        path = self.protocol + '://' + (host or self.server_name()) + path
 
361
        return path
 
362
 
 
363
    def get_proxy_auth_header(self):
 
364
        auth = base64.encodestring(self.proxy_user+':'+self.proxy_pass)
 
365
        return {'Proxy-Authorization': 'Basic %s' % auth}
 
366
 
 
367
    def _mexe(self, method, path, data, headers, host=None, sender=None):
 
368
        """
 
369
        mexe - Multi-execute inside a loop, retrying multiple times to handle
 
370
               transient Internet errors by simply trying again.
 
371
               Also handles redirects.
 
372
 
 
373
        This code was inspired by the S3Utils classes posted to the boto-users
 
374
        Google group by Larry Bates.  Thanks!
 
375
        """
 
376
        boto.log.debug('Method: %s' % method)
 
377
        boto.log.debug('Path: %s' % path)
 
378
        boto.log.debug('Data: %s' % data)
 
379
        boto.log.debug('Headers: %s' % headers)
 
380
        boto.log.debug('Host: %s' % host)
 
381
        response = None
 
382
        body = None
 
383
        e = None
 
384
        num_retries = config.getint('Boto', 'num_retries', self.num_retries)
 
385
        i = 0
 
386
        connection = self.get_http_connection(host, self.is_secure)
 
387
        while i <= num_retries:
 
388
            try:
 
389
                if callable(sender):
 
390
                    response = sender(connection, method, path, data, headers)
 
391
                else:
 
392
                    connection.request(method, path, data, headers)
 
393
                    response = connection.getresponse()
 
394
                location = response.getheader('location')
 
395
                # -- gross hack --
 
396
                # httplib gets confused with chunked responses to HEAD requests
 
397
                # so I have to fake it out
 
398
                if method == 'HEAD' and getattr(response, 'chunked', False):
 
399
                    response.chunked = 0
 
400
                if response.status == 500 or response.status == 503:
 
401
                    boto.log.debug('received %d response, retrying in %d seconds' % (response.status, 2**i))
 
402
                    body = response.read()
 
403
                elif response.status == 408:
 
404
                    body = response.read()
 
405
                    print '-------------------------'
 
406
                    print '         4 0 8           '
 
407
                    print 'path=%s' % path
 
408
                    print body
 
409
                    print '-------------------------'
 
410
                elif response.status < 300 or response.status >= 400 or \
 
411
                        not location:
 
412
                    self.put_http_connection(host, self.is_secure, connection)
 
413
                    return response
 
414
                else:
 
415
                    scheme, host, path, params, query, fragment = \
 
416
                            urlparse.urlparse(location)
 
417
                    if query:
 
418
                        path += '?' + query
 
419
                    boto.log.debug('Redirecting: %s' % scheme + '://' + host + path)
 
420
                    connection = self.get_http_connection(host,
 
421
                            scheme == 'https')
 
422
                    continue
 
423
            except KeyboardInterrupt:
 
424
                sys.exit('Keyboard Interrupt')
 
425
            except self.http_exceptions, e:
 
426
                boto.log.debug('encountered %s exception, reconnecting' % \
 
427
                                  e.__class__.__name__)
 
428
                connection = self.new_http_connection(host, self.is_secure)
 
429
            time.sleep(2**i)
 
430
            i += 1
 
431
        # If we made it here, it's because we have exhausted our retries and stil haven't
 
432
        # succeeded.  So, if we have a response object, use it to raise an exception.
 
433
        # Otherwise, raise the exception that must have already happened.
 
434
        if response:
 
435
            raise BotoServerError(response.status, response.reason, body)
 
436
        elif e:
 
437
            raise e
 
438
        else:
 
439
            raise BotoClientError('Please report this exception as a Boto Issue!')
 
440
 
 
441
    def make_request(self, method, path, headers=None, data='', host=None,
 
442
                     auth_path=None, sender=None):
 
443
        path = self.get_path(path)
 
444
        if headers == None:
 
445
            headers = {}
 
446
        else:
 
447
            headers = headers.copy()
 
448
        headers['User-Agent'] = UserAgent
 
449
        if not headers.has_key('Content-Length'):
 
450
            headers['Content-Length'] = str(len(data))
 
451
        if self.use_proxy:
 
452
            path = self.prefix_proxy_to_path(path, host)
 
453
            if self.proxy_user and self.proxy_pass and not self.is_secure:
 
454
                # If is_secure, we don't have to set the proxy authentication
 
455
                # header here, we did that in the CONNECT to the proxy.
 
456
                headers.update(self.get_proxy_auth_header())
 
457
        request_string = auth_path or path
 
458
        self.add_aws_auth_header(headers, method, request_string)
 
459
        return self._mexe(method, path, data, headers, host, sender)
 
460
 
 
461
    def add_aws_auth_header(self, headers, method, path):
 
462
        path = self.get_path(path)
 
463
        if not headers.has_key('Date'):
 
464
            headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
 
465
                                            time.gmtime())
 
466
 
 
467
        c_string = boto.utils.canonical_string(method, path, headers)
 
468
        boto.log.debug('Canonical: %s' % c_string)
 
469
        hmac = self.hmac.copy()
 
470
        hmac.update(c_string)
 
471
        b64_hmac = base64.encodestring(hmac.digest()).strip()
 
472
        headers['Authorization'] = "AWS %s:%s" % (self.aws_access_key_id, b64_hmac)
 
473
 
 
474
    def close(self):
 
475
        """(Optional) Close any open HTTP connections.  This is non-destructive,
 
476
        and making a new request will open a connection again."""
 
477
 
 
478
        boto.log.debug('closing all HTTP connections')
 
479
        self.connection = None  # compat field
 
480
 
 
481
 
 
482
class AWSQueryConnection(AWSAuthConnection):
 
483
 
 
484
    APIVersion = ''
 
485
    SignatureVersion = '1'
 
486
    ResponseError = BotoServerError
 
487
 
 
488
    def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
 
489
                 is_secure=True, port=None, proxy=None, proxy_port=None,
 
490
                 proxy_user=None, proxy_pass=None, host=None, debug=0,
 
491
                 https_connection_factory=None, path='/'):
 
492
        AWSAuthConnection.__init__(self, host, aws_access_key_id, aws_secret_access_key,
 
493
                                   is_secure, port, proxy, proxy_port, proxy_user, proxy_pass,
 
494
                                   debug,  https_connection_factory, path)
 
495
 
 
496
    def get_utf8_value(self, value):
 
497
        if not isinstance(value, str) and not isinstance(value, unicode):
 
498
            value = str(value)
 
499
        if isinstance(value, unicode):
 
500
            return value.encode('utf-8')
 
501
        else:
 
502
            return value
 
503
 
 
504
    def calc_signature_0(self, params):
 
505
        boto.log.debug('using calc_signature_0')
 
506
        hmac = self.hmac.copy()
 
507
        s = params['Action'] + params['Timestamp']
 
508
        hmac.update(s)
 
509
        keys = params.keys()
 
510
        keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
 
511
        pairs = []
 
512
        for key in keys:
 
513
            val = self.get_utf8_value(params[key])
 
514
            pairs.append(key + '=' + urllib.quote(val))
 
515
        qs = '&'.join(pairs)
 
516
        return (qs, base64.b64encode(hmac.digest()))
 
517
 
 
518
    def calc_signature_1(self, params):
 
519
        boto.log.debug('using calc_signature_1')
 
520
        hmac = self.hmac.copy()
 
521
        keys = params.keys()
 
522
        keys.sort(cmp = lambda x, y: cmp(x.lower(), y.lower()))
 
523
        pairs = []
 
524
        for key in keys:
 
525
            hmac.update(key)
 
526
            val = self.get_utf8_value(params[key])
 
527
            hmac.update(val)
 
528
            pairs.append(key + '=' + urllib.quote(val))
 
529
        qs = '&'.join(pairs)
 
530
        return (qs, base64.b64encode(hmac.digest()))
 
531
 
 
532
    def calc_signature_2(self, params, verb, path):
 
533
        boto.log.debug('using calc_signature_2')
 
534
        string_to_sign = '%s\n%s\n%s\n' % (verb, self.server_name().lower(), path)
 
535
        if self.hmac_256:
 
536
            hmac = self.hmac_256.copy()
 
537
            params['SignatureMethod'] = 'HmacSHA256'
 
538
        else:
 
539
            hmac = self.hmac.copy()
 
540
            params['SignatureMethod'] = 'HmacSHA1'
 
541
        keys = params.keys()
 
542
        keys.sort()
 
543
        pairs = []
 
544
        for key in keys:
 
545
            val = self.get_utf8_value(params[key])
 
546
            pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
 
547
        qs = '&'.join(pairs)
 
548
        boto.log.debug('query string: %s' % qs)
 
549
        string_to_sign += qs
 
550
        boto.log.debug('string_to_sign: %s' % string_to_sign)
 
551
        hmac.update(string_to_sign)
 
552
        b64 = base64.b64encode(hmac.digest())
 
553
        boto.log.debug('len(b64)=%d' % len(b64))
 
554
        boto.log.debug('base64 encoded digest: %s' % b64)
 
555
        return (qs, b64)
 
556
 
 
557
    def get_signature(self, params, verb, path):
 
558
        if self.SignatureVersion == '0':
 
559
            t = self.calc_signature_0(params)
 
560
        elif self.SignatureVersion == '1':
 
561
            t = self.calc_signature_1(params)
 
562
        elif self.SignatureVersion == '2':
 
563
            t = self.calc_signature_2(params, verb, path)
 
564
        else:
 
565
            raise BotoClientError('Unknown Signature Version: %s' % self.SignatureVersion)
 
566
        return t
 
567
 
 
568
    def make_request(self, action, params=None, path='/', verb='GET'):
 
569
        headers = {}
 
570
        if params == None:
 
571
            params = {}
 
572
        params['Action'] = action
 
573
        params['Version'] = self.APIVersion
 
574
        params['AWSAccessKeyId'] = self.aws_access_key_id
 
575
        params['SignatureVersion'] = self.SignatureVersion
 
576
        params['Timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
 
577
        qs, signature = self.get_signature(params, verb, self.get_path(path))
 
578
        if verb == 'POST':
 
579
            headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
 
580
            request_body = qs + '&Signature=' + urllib.quote(signature)
 
581
            qs = path
 
582
        else:
 
583
            request_body = ''
 
584
            qs = path + '?' + qs + '&Signature=' + urllib.quote(signature)
 
585
        return AWSAuthConnection.make_request(self, verb, qs,
 
586
                                              data=request_body,
 
587
                                              headers=headers)
 
588
 
 
589
    def build_list_params(self, params, items, label):
 
590
        if isinstance(items, str):
 
591
            items = [items]
 
592
        for i in range(1, len(items)+1):
 
593
            params['%s.%d' % (label, i)] = items[i-1]
 
594
 
 
595
    # generics
 
596
 
 
597
    def get_list(self, action, params, markers, path='/', parent=None, verb='GET'):
 
598
        if not parent:
 
599
            parent = self
 
600
        response = self.make_request(action, params, path, verb)
 
601
        body = response.read()
 
602
        boto.log.debug(body)
 
603
        if response.status == 200:
 
604
            rs = ResultSet(markers)
 
605
            h = handler.XmlHandler(rs, parent)
 
606
            xml.sax.parseString(body, h)
 
607
            return rs
 
608
        else:
 
609
            boto.log.error('%s %s' % (response.status, response.reason))
 
610
            boto.log.error('%s' % body)
 
611
            raise self.ResponseError(response.status, response.reason, body)
 
612
 
 
613
    def get_object(self, action, params, cls, path='/', parent=None, verb='GET'):
 
614
        if not parent:
 
615
            parent = self
 
616
        response = self.make_request(action, params, path, verb)
 
617
        body = response.read()
 
618
        boto.log.debug(body)
 
619
        if response.status == 200:
 
620
            obj = cls(parent)
 
621
            h = handler.XmlHandler(obj, parent)
 
622
            xml.sax.parseString(body, h)
 
623
            return obj
 
624
        else:
 
625
            boto.log.error('%s %s' % (response.status, response.reason))
 
626
            boto.log.error('%s' % body)
 
627
            raise self.ResponseError(response.status, response.reason, body)
 
628
 
 
629
    def get_status(self, action, params, path='/', parent=None, verb='GET'):
 
630
        if not parent:
 
631
            parent = self
 
632
        response = self.make_request(action, params, path, verb)
 
633
        body = response.read()
 
634
        boto.log.debug(body)
 
635
        if response.status == 200:
 
636
            rs = ResultSet()
 
637
            h = handler.XmlHandler(rs, parent)
 
638
            xml.sax.parseString(body, h)
 
639
            return rs.status
 
640
        else:
 
641
            boto.log.error('%s %s' % (response.status, response.reason))
 
642
            boto.log.error('%s' % body)
 
643
            raise self.ResponseError(response.status, response.reason, body)
 
644