Blob Blame History Raw
diff -urP oauth2-1.1.3/debian/changelog python-oauth2/debian/changelog
--- oauth2-1.1.3/debian/changelog	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/debian/changelog	2010-10-22 13:09:08.123317001 -0400
@@ -0,0 +1,60 @@
+python-oauth2 (1.2.1) lucid; urgency=low
+
+  [ Joe Stump ]
+  * Fixed a minor spelling error, added tests for
+    oauth2.Error.__str__(), and added a test for build_xoauth_string().
+  * Added a 'make test' command to the Makefile.
+  * Added a bunch of new tests. 100% coverage has lapsed a bit. Fixing
+    now.
+
+  [ Ian Eure ]
+  * Update deb rule in makefile to sign packages and move them to
+    dist/deb.
+
+  [ Joe Stump ]
+  * Implemented a 'fix' for a bug in certain OAuth providers.
+    http://bit.ly/aUrH43
+
+  [ Ian Eure ]
+  * Update packaging.
+  * Fix version disparity.
+
+  [ coulix ]
+  * get normalised parameters should not take into account oauth_ params
+
+  [ Mathias Herberts ]
+  * Modified get_normalized_parameters so it does not encode '~' for
+    full conformance with RFC 5849 section 3.6
+
+  [ Peter Bengtsson ]
+  * made it python 2.4 compatible
+
+  [ Roderic Campbell ]
+  * tok != token. con != consumer. May be confusing for people not
+    actually reading the code.
+
+  [ Ian Eure ]
+
+ -- Ian Eure <ian@simplegeo.com>  Mon, 28 Jun 2010 12:02:02 -0700
+
+python-oauth2 (1.2.1pre2) unstable; urgency=low
+
+  * Fix version disparity between setup.py & debian/changelog.
+
+ -- Ian Eure <ian@simplegeo.com>  Fri, 11 Jun 2010 16:11:41 -0700
+
+python-oauth2 (1.2.1pre1) unstable; urgency=low
+
+  * Make a native package.
+  * Increment version.
+  * Make debian/rules executable.
+  * Update standards-version, maintainers.
+  * Remvoe unneeded python-central build dependency.
+
+ -- Ian Eure <ian@simplegeo.com>  Fri, 11 Jun 2010 16:09:01 -0700
+
+python-oauth2 (1.0.0-1simplegeo01) karmic; urgency=low
+
+  * Initial build for SimpleGeo
+
+ -- SimpleGeo Nerds <nerds@simplegeo.com>  Wed, 21 Oct 2009 23:24:00 -0700
diff -urP oauth2-1.1.3/debian/compat python-oauth2/debian/compat
--- oauth2-1.1.3/debian/compat	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/debian/compat	2010-10-22 13:09:08.123317001 -0400
@@ -0,0 +1 @@
+5
diff -urP oauth2-1.1.3/debian/control python-oauth2/debian/control
--- oauth2-1.1.3/debian/control	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/debian/control	2010-10-22 13:09:08.123317001 -0400
@@ -0,0 +1,24 @@
+Source: python-oauth2
+Section: python
+Priority: optional
+Maintainer: SimpleGeo Nerds <nerds@simplegeo.com>
+Uploaders: Chris Lea <chris.lea@gmail.com>, Ian Eure <ian@simplegeo.com>
+Standards-Version: 3.8.4
+XS-Python-Version: all
+Build-Depends: debhelper (>= 4.1.13), cdbs (>= 0.4.49), python, python-setuptools, python-support
+Homepage: http://github.com/simplegeo/python-oauth2
+
+Package: python-oauth2
+Architecture: all
+XB-Python-Version: ${python:Versions}
+Depends: ${python:Depends}, python-httplib2
+Provides: ${python:Provides}
+Suggests:
+Description: A Python OAuth class with several useful
+ features.
+ .
+ * 100% unit test coverage.
+ * The Request class now extends from dict.
+ * The Client class works and extends from httplib2.
+   It's a thin wrapper that handles automatically signing
+   any normal HTTP request you might wish to make.
diff -urP oauth2-1.1.3/debian/copyright python-oauth2/debian/copyright
--- oauth2-1.1.3/debian/copyright	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/debian/copyright	2010-10-22 13:09:08.123317001 -0400
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2007 Leah Culver
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff -urP oauth2-1.1.3/debian/pycompat python-oauth2/debian/pycompat
--- oauth2-1.1.3/debian/pycompat	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/debian/pycompat	2010-10-22 13:09:08.123317001 -0400
@@ -0,0 +1 @@
+2
diff -urP oauth2-1.1.3/debian/pyversions python-oauth2/debian/pyversions
--- oauth2-1.1.3/debian/pyversions	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/debian/pyversions	2010-10-22 13:09:08.123317001 -0400
@@ -0,0 +1 @@
+2.5-
diff -urP oauth2-1.1.3/debian/rules python-oauth2/debian/rules
--- oauth2-1.1.3/debian/rules	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/debian/rules	2010-10-22 13:09:08.123317001 -0400
@@ -0,0 +1,8 @@
+#!/usr/bin/make -f
+# -*- mode: makefile; coding: utf-8 -*-
+
+DEB_PYTHON_SYSTEM = pysupport
+
+include /usr/share/cdbs/1/rules/debhelper.mk
+include /usr/share/cdbs/1/class/python-distutils.mk
+
diff -urP oauth2-1.1.3/example/appengine_oauth.py python-oauth2/example/appengine_oauth.py
--- oauth2-1.1.3/example/appengine_oauth.py	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/example/appengine_oauth.py	2010-10-22 13:09:08.124317001 -0400
@@ -0,0 +1,108 @@
+"""
+The MIT License
+
+Copyright (c) 2010 Justin Plock
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import os
+
+from google.appengine.ext import webapp
+from google.appengine.ext import db
+from google.appengine.ext.webapp import util
+import oauth2 as oauth # httplib2 is required for this to work on AppEngine
+
+class Client(db.Model):
+    # oauth_key is the Model's key_name field
+    oauth_secret = db.StringProperty() # str(uuid.uuid4()) works well for this
+    first_name = db.StringProperty()
+    last_name = db.StringProperty()
+    email_address = db.EmailProperty(required=True)
+    password = db.StringProperty(required=True)
+
+    @property
+    def secret(self):
+        return self.oauth_secret
+
+class OAuthHandler(webapp.RequestHandler):
+
+    def __init__(self):
+        self._server = oauth.Server()
+        self._server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+        self._server.add_signature_method(oauth.SignatureMethod_PLAINTEXT())
+
+    def get_oauth_request(self):
+        """Return an OAuth Request object for the current request."""
+
+        try:
+            method = os.environ['REQUEST_METHOD']
+        except:
+            method = 'GET'
+
+        postdata = None
+        if method in ('POST', 'PUT'):
+            postdata = self.request.body
+
+        return oauth.Request.from_request(method, self.request.uri,
+            headers=self.request.headers, query_string=postdata)
+
+    def get_client(self, request=None):
+        """Return the client from the OAuth parameters."""
+
+        if not isinstance(request, oauth.Request):
+            request = self.get_oauth_request()
+        client_key = request.get_parameter('oauth_consumer_key')
+        if not client_key:
+            raise Exception('Missing "oauth_consumer_key" parameter in ' \
+                'OAuth "Authorization" header')
+
+        client = models.Client.get_by_key_name(client_key)
+        if not client:
+            raise Exception('Client "%s" not found.' % client_key)
+
+        return client
+
+    def is_valid(self):
+        """Returns a Client object if this is a valid OAuth request."""
+
+        try:
+            request = self.get_oauth_request()
+            client = self.get_client(request)
+            params = self._server.verify_request(request, client, None)
+        except Exception, e:
+            raise e
+
+        return client
+
+class SampleHandler(OAuthHandler):
+    def get(self):
+        try:
+            client = self.is_valid()
+        except Exception, e:
+            self.error(500)
+            self.response.out.write(e)
+
+def main():
+    application = webapp.WSGIApplication([(r'/sample', SampleHandler)],
+        debug=False)
+    util.run_wsgi_app(application)
+
+if __name__ == '__main__':
+    main()
diff -urP oauth2-1.1.3/example/client.py python-oauth2/example/client.py
--- oauth2-1.1.3/example/client.py	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/example/client.py	2010-10-22 13:09:08.124317001 -0400
@@ -0,0 +1,165 @@
+"""
+The MIT License
+
+Copyright (c) 2007 Leah Culver
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Example consumer. This is not recommended for production.
+Instead, you'll want to create your own subclass of OAuthClient
+or find one that works with your web framework.
+"""
+
+import httplib
+import time
+import oauth.oauth as oauth
+
+# settings for the local test consumer
+SERVER = 'localhost'
+PORT = 8080
+
+# fake urls for the test server (matches ones in server.py)
+REQUEST_TOKEN_URL = 'https://photos.example.net/request_token'
+ACCESS_TOKEN_URL = 'https://photos.example.net/access_token'
+AUTHORIZATION_URL = 'https://photos.example.net/authorize'
+CALLBACK_URL = 'http://printer.example.com/request_token_ready'
+RESOURCE_URL = 'http://photos.example.net/photos'
+
+# key and secret granted by the service provider for this consumer application - same as the MockOAuthDataStore
+CONSUMER_KEY = 'key'
+CONSUMER_SECRET = 'secret'
+
+# example client using httplib with headers
+class SimpleOAuthClient(oauth.OAuthClient):
+
+    def __init__(self, server, port=httplib.HTTP_PORT, request_token_url='', access_token_url='', authorization_url=''):
+        self.server = server
+        self.port = port
+        self.request_token_url = request_token_url
+        self.access_token_url = access_token_url
+        self.authorization_url = authorization_url
+        self.connection = httplib.HTTPConnection("%s:%d" % (self.server, self.port))
+
+    def fetch_request_token(self, oauth_request):
+        # via headers
+        # -> OAuthToken
+        self.connection.request(oauth_request.http_method, self.request_token_url, headers=oauth_request.to_header()) 
+        response = self.connection.getresponse()
+        return oauth.OAuthToken.from_string(response.read())
+
+    def fetch_access_token(self, oauth_request):
+        # via headers
+        # -> OAuthToken
+        self.connection.request(oauth_request.http_method, self.access_token_url, headers=oauth_request.to_header()) 
+        response = self.connection.getresponse()
+        return oauth.OAuthToken.from_string(response.read())
+
+    def authorize_token(self, oauth_request):
+        # via url
+        # -> typically just some okay response
+        self.connection.request(oauth_request.http_method, oauth_request.to_url()) 
+        response = self.connection.getresponse()
+        return response.read()
+
+    def access_resource(self, oauth_request):
+        # via post body
+        # -> some protected resources
+        headers = {'Content-Type' :'application/x-www-form-urlencoded'}
+        self.connection.request('POST', RESOURCE_URL, body=oauth_request.to_postdata(), headers=headers)
+        response = self.connection.getresponse()
+        return response.read()
+
+def run_example():
+
+    # setup
+    print '** OAuth Python Library Example **'
+    client = SimpleOAuthClient(SERVER, PORT, REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL)
+    consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET)
+    signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT()
+    signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
+    pause()
+
+    # get request token
+    print '* Obtain a request token ...'
+    pause()
+    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, callback=CALLBACK_URL, http_url=client.request_token_url)
+    oauth_request.sign_request(signature_method_plaintext, consumer, None)
+    print 'REQUEST (via headers)'
+    print 'parameters: %s' % str(oauth_request.parameters)
+    pause()
+    token = client.fetch_request_token(oauth_request)
+    print 'GOT'
+    print 'key: %s' % str(token.key)
+    print 'secret: %s' % str(token.secret)
+    print 'callback confirmed? %s' % str(token.callback_confirmed)
+    pause()
+
+    print '* Authorize the request token ...'
+    pause()
+    oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=client.authorization_url)
+    print 'REQUEST (via url query string)'
+    print 'parameters: %s' % str(oauth_request.parameters)
+    pause()
+    # this will actually occur only on some callback
+    response = client.authorize_token(oauth_request)
+    print 'GOT'
+    print response
+    # sad way to get the verifier
+    import urlparse, cgi
+    query = urlparse.urlparse(response)[4]
+    params = cgi.parse_qs(query, keep_blank_values=False)
+    verifier = params['oauth_verifier'][0]
+    print 'verifier: %s' % verifier
+    pause()
+
+    # get access token
+    print '* Obtain an access token ...'
+    pause()
+    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, verifier=verifier, http_url=client.access_token_url)
+    oauth_request.sign_request(signature_method_plaintext, consumer, token)
+    print 'REQUEST (via headers)'
+    print 'parameters: %s' % str(oauth_request.parameters)
+    pause()
+    token = client.fetch_access_token(oauth_request)
+    print 'GOT'
+    print 'key: %s' % str(token.key)
+    print 'secret: %s' % str(token.secret)
+    pause()
+
+    # access some protected resources
+    print '* Access protected resources ...'
+    pause()
+    parameters = {'file': 'vacation.jpg', 'size': 'original'} # resource specific params
+    oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_method='POST', http_url=RESOURCE_URL, parameters=parameters)
+    oauth_request.sign_request(signature_method_hmac_sha1, consumer, token)
+    print 'REQUEST (via post body)'
+    print 'parameters: %s' % str(oauth_request.parameters)
+    pause()
+    params = client.access_resource(oauth_request)
+    print 'GOT'
+    print 'non-oauth parameters: %s' % params
+    pause()
+
+def pause():
+    print ''
+    time.sleep(1)
+
+if __name__ == '__main__':
+    run_example()
+    print 'Done.'
\ No newline at end of file
diff -urP oauth2-1.1.3/example/server.py python-oauth2/example/server.py
--- oauth2-1.1.3/example/server.py	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/example/server.py	2010-10-22 13:09:08.124317001 -0400
@@ -0,0 +1,195 @@
+"""
+The MIT License
+
+Copyright (c) 2007 Leah Culver
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+import urllib
+
+import oauth.oauth as oauth
+
+# fake urls for the test server
+REQUEST_TOKEN_URL = 'https://photos.example.net/request_token'
+ACCESS_TOKEN_URL = 'https://photos.example.net/access_token'
+AUTHORIZATION_URL = 'https://photos.example.net/authorize'
+CALLBACK_URL = 'http://printer.example.com/request_token_ready'
+RESOURCE_URL = 'http://photos.example.net/photos'
+REALM = 'http://photos.example.net/'
+VERIFIER = 'verifier'
+
+# example store for one of each thing
+class MockOAuthDataStore(oauth.OAuthDataStore):
+
+    def __init__(self):
+        self.consumer = oauth.OAuthConsumer('key', 'secret')
+        self.request_token = oauth.OAuthToken('requestkey', 'requestsecret')
+        self.access_token = oauth.OAuthToken('accesskey', 'accesssecret')
+        self.nonce = 'nonce'
+        self.verifier = VERIFIER
+
+    def lookup_consumer(self, key):
+        if key == self.consumer.key:
+            return self.consumer
+        return None
+
+    def lookup_token(self, token_type, token):
+        token_attrib = getattr(self, '%s_token' % token_type)
+        if token == token_attrib.key:
+            ## HACK
+            token_attrib.set_callback(CALLBACK_URL)
+            return token_attrib
+        return None
+
+    def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
+        if oauth_token and oauth_consumer.key == self.consumer.key and (oauth_token.key == self.request_token.key or oauth_token.key == self.access_token.key) and nonce == self.nonce:
+            return self.nonce
+        return None
+
+    def fetch_request_token(self, oauth_consumer, oauth_callback):
+        if oauth_consumer.key == self.consumer.key:
+            if oauth_callback:
+                # want to check here if callback is sensible
+                # for mock store, we assume it is
+                self.request_token.set_callback(oauth_callback)
+            return self.request_token
+        return None
+
+    def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
+        if oauth_consumer.key == self.consumer.key and oauth_token.key == self.request_token.key and oauth_verifier == self.verifier:
+            # want to check here if token is authorized
+            # for mock store, we assume it is
+            return self.access_token
+        return None
+
+    def authorize_request_token(self, oauth_token, user):
+        if oauth_token.key == self.request_token.key:
+            # authorize the request token in the store
+            # for mock store, do nothing
+            return self.request_token
+        return None
+
+class RequestHandler(BaseHTTPRequestHandler):
+
+    def __init__(self, *args, **kwargs):
+        self.oauth_server = oauth.OAuthServer(MockOAuthDataStore())
+        self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT())
+        self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())
+        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
+
+    # example way to send an oauth error
+    def send_oauth_error(self, err=None):
+        # send a 401 error
+        self.send_error(401, str(err.message))
+        # return the authenticate header
+        header = oauth.build_authenticate_header(realm=REALM)
+        for k, v in header.iteritems():
+            self.send_header(k, v) 
+
+    def do_GET(self):
+
+        # debug info
+        #print self.command, self.path, self.headers
+        
+        # get the post data (if any)
+        postdata = None
+        if self.command == 'POST':
+            try:
+                length = int(self.headers.getheader('content-length'))
+                postdata = self.rfile.read(length)
+            except:
+                pass
+
+        # construct the oauth request from the request parameters
+        oauth_request = oauth.OAuthRequest.from_request(self.command, self.path, headers=self.headers, query_string=postdata)
+
+        # request token
+        if self.path.startswith(REQUEST_TOKEN_URL):
+            try:
+                # create a request token
+                token = self.oauth_server.fetch_request_token(oauth_request)
+                # send okay response
+                self.send_response(200, 'OK')
+                self.end_headers()
+                # return the token
+                self.wfile.write(token.to_string())
+            except oauth.OAuthError, err:
+                self.send_oauth_error(err)
+            return
+
+        # user authorization
+        if self.path.startswith(AUTHORIZATION_URL):
+            try:
+                # get the request token
+                token = self.oauth_server.fetch_request_token(oauth_request)
+                # authorize the token (kind of does nothing for now)
+                token = self.oauth_server.authorize_token(token, None)
+                token.set_verifier(VERIFIER)
+                # send okay response
+                self.send_response(200, 'OK')
+                self.end_headers()
+                # return the callback url (to show server has it)
+                self.wfile.write(token.get_callback_url())
+            except oauth.OAuthError, err:
+                self.send_oauth_error(err)
+            return
+
+        # access token
+        if self.path.startswith(ACCESS_TOKEN_URL):
+            try:
+                # create an access token
+                token = self.oauth_server.fetch_access_token(oauth_request)
+                # send okay response
+                self.send_response(200, 'OK')
+                self.end_headers()
+                # return the token
+                self.wfile.write(token.to_string())
+            except oauth.OAuthError, err:
+                self.send_oauth_error(err)
+            return
+
+        # protected resources
+        if self.path.startswith(RESOURCE_URL):
+            try:
+                # verify the request has been oauth authorized
+                consumer, token, params = self.oauth_server.verify_request(oauth_request)
+                # send okay response
+                self.send_response(200, 'OK')
+                self.end_headers()
+                # return the extra parameters - just for something to return
+                self.wfile.write(str(params))
+            except oauth.OAuthError, err:
+                self.send_oauth_error(err)
+            return
+
+    def do_POST(self):
+        return self.do_GET()
+
+def main():
+    try:
+        server = HTTPServer(('', 8080), RequestHandler)
+        print 'Test server running...'
+        server.serve_forever()
+    except KeyboardInterrupt:
+        server.socket.close()
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file
diff -urP oauth2-1.1.3/.git/config python-oauth2/.git/config
--- oauth2-1.1.3/.git/config	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/config	2010-10-22 13:09:08.119317001 -0400
@@ -0,0 +1,11 @@
+[core]
+	repositoryformatversion = 0
+	filemode = true
+	bare = false
+	logallrefupdates = true
+[remote "origin"]
+	fetch = +refs/heads/*:refs/remotes/origin/*
+	url = http://github.com/simplegeo/python-oauth2.git
+[branch "master"]
+	remote = origin
+	merge = refs/heads/master
diff -urP oauth2-1.1.3/.git/description python-oauth2/.git/description
--- oauth2-1.1.3/.git/description	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/description	2010-10-22 13:09:06.816317000 -0400
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff -urP oauth2-1.1.3/.git/HEAD python-oauth2/.git/HEAD
--- oauth2-1.1.3/.git/HEAD	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/HEAD	2010-10-22 13:09:08.117316999 -0400
@@ -0,0 +1 @@
+ref: refs/heads/master
diff -urP oauth2-1.1.3/.git/hooks/applypatch-msg.sample python-oauth2/.git/hooks/applypatch-msg.sample
--- oauth2-1.1.3/.git/hooks/applypatch-msg.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/applypatch-msg.sample	2010-10-22 13:09:06.816317000 -0400
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.  The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "applypatch-msg".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+	exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
diff -urP oauth2-1.1.3/.git/hooks/commit-msg.sample python-oauth2/.git/hooks/commit-msg.sample
--- oauth2-1.1.3/.git/hooks/commit-msg.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/commit-msg.sample	2010-10-22 13:09:06.817317000 -0400
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message.  The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit.  The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
+	echo >&2 Duplicate Signed-off-by lines.
+	exit 1
+}
diff -urP oauth2-1.1.3/.git/hooks/post-commit.sample python-oauth2/.git/hooks/post-commit.sample
--- oauth2-1.1.3/.git/hooks/post-commit.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/post-commit.sample	2010-10-22 13:09:06.817317000 -0400
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, rename this file to "post-commit".
+
+: Nothing
diff -urP oauth2-1.1.3/.git/hooks/post-receive.sample python-oauth2/.git/hooks/post-receive.sample
--- oauth2-1.1.3/.git/hooks/post-receive.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/post-receive.sample	2010-10-22 13:09:06.816317000 -0400
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script for the "post-receive" event.
+#
+# The "post-receive" script is run after receive-pack has accepted a pack
+# and the repository has been updated.  It is passed arguments in through
+# stdin in the form
+#  <oldrev> <newrev> <refname>
+# For example:
+#  aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
+#
+# see contrib/hooks/ for a sample, or uncomment the next line and
+# rename the file to "post-receive".
+
+#. /usr/share/git-core/contrib/hooks/post-receive-email
diff -urP oauth2-1.1.3/.git/hooks/post-update.sample python-oauth2/.git/hooks/post-update.sample
--- oauth2-1.1.3/.git/hooks/post-update.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/post-update.sample	2010-10-22 13:09:06.817317000 -0400
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, rename this file to "post-update".
+
+exec git update-server-info
diff -urP oauth2-1.1.3/.git/hooks/pre-applypatch.sample python-oauth2/.git/hooks/pre-applypatch.sample
--- oauth2-1.1.3/.git/hooks/pre-applypatch.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/pre-applypatch.sample	2010-10-22 13:09:06.817317000 -0400
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-applypatch".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+	exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
diff -urP oauth2-1.1.3/.git/hooks/pre-commit.sample python-oauth2/.git/hooks/pre-commit.sample
--- oauth2-1.1.3/.git/hooks/pre-commit.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/pre-commit.sample	2010-10-22 13:09:06.816317000 -0400
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments.  The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+	against=HEAD
+else
+	# Initial commit: diff against an empty tree object
+	against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+# If you want to allow non-ascii filenames set this variable to true.
+allownonascii=$(git config hooks.allownonascii)
+
+# Cross platform projects tend to avoid non-ascii filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+	# Note that the use of brackets around a tr range is ok here, (it's
+	# even required, for portability to Solaris 10's /usr/bin/tr), since
+	# the square bracket bytes happen to fall in the designated range.
+	test "$(git diff --cached --name-only --diff-filter=A -z $against |
+	  LC_ALL=C tr -d '[ -~]\0')"
+then
+	echo "Error: Attempt to add a non-ascii file name."
+	echo
+	echo "This can cause problems if you want to work"
+	echo "with people on other platforms."
+	echo
+	echo "To be portable it is advisable to rename the file ..."
+	echo
+	echo "If you know what you are doing you can disable this"
+	echo "check using:"
+	echo
+	echo "  git config hooks.allownonascii true"
+	echo
+	exit 1
+fi
+
+exec git diff-index --check --cached $against --
diff -urP oauth2-1.1.3/.git/hooks/prepare-commit-msg.sample python-oauth2/.git/hooks/prepare-commit-msg.sample
--- oauth2-1.1.3/.git/hooks/prepare-commit-msg.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/prepare-commit-msg.sample	2010-10-22 13:09:06.817317000 -0400
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by "git commit" with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source.  The hook's purpose is to edit the commit
+# message file.  If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, rename this file to "prepare-commit-msg".
+
+# This hook includes three examples.  The first comments out the
+# "Conflicts:" part of a merge commit.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output.  It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited.  This is rarely a good idea.
+
+case "$2,$3" in
+  merge,)
+    /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
+
+# ,|template,)
+#   /usr/bin/perl -i.bak -pe '
+#      print "\n" . `git diff --cached --name-status -r`
+#	 if /^#/ && $first++ == 0' "$1" ;;
+
+  *) ;;
+esac
+
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
diff -urP oauth2-1.1.3/.git/hooks/pre-rebase.sample python-oauth2/.git/hooks/pre-rebase.sample
--- oauth2-1.1.3/.git/hooks/pre-rebase.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/pre-rebase.sample	2010-10-22 13:09:06.817317000 -0400
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, 2008 Junio C Hamano
+#
+# The "pre-rebase" hook is run just before "git rebase" starts doing
+# its job, and can prevent the command from running by exiting with
+# non-zero status.
+#
+# The hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+#
+# This sample shows how to prevent topic branches that are already
+# merged to 'next' branch from getting rebased, because allowing it
+# would result in rebasing already published history.
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+	topic="refs/heads/$2"
+else
+	topic=`git symbolic-ref HEAD` ||
+	exit 0 ;# we do not interrupt rebasing detached HEAD
+fi
+
+case "$topic" in
+refs/heads/??/*)
+	;;
+*)
+	exit 0 ;# we do not interrupt others.
+	;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master.  Is it OK to rebase it?
+
+# Does the topic really exist?
+git show-ref -q "$topic" || {
+	echo >&2 "No such branch $topic"
+	exit 1
+}
+
+# Is topic fully merged to master?
+not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+	echo >&2 "$topic is fully merged to master; better remove it."
+	exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next?  If so you should not be rebasing it.
+only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git rev-list ^master           ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+	not_in_topic=`git rev-list "^$topic" master`
+	if test -z "$not_in_topic"
+	then
+		echo >&2 "$topic is already up-to-date with master"
+		exit 1 ;# we could allow it, but there is no point.
+	else
+		exit 0
+	fi
+else
+	not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
+	/usr/bin/perl -e '
+		my $topic = $ARGV[0];
+		my $msg = "* $topic has commits already merged to public branch:\n";
+		my (%not_in_next) = map {
+			/^([0-9a-f]+) /;
+			($1 => 1);
+		} split(/\n/, $ARGV[1]);
+		for my $elem (map {
+				/^([0-9a-f]+) (.*)$/;
+				[$1 => $2];
+			} split(/\n/, $ARGV[2])) {
+			if (!exists $not_in_next{$elem->[0]}) {
+				if ($msg) {
+					print STDERR $msg;
+					undef $msg;
+				}
+				print STDERR " $elem->[1]\n";
+			}
+		}
+	' "$topic" "$not_in_next" "$not_in_master"
+	exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+   merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+   it is deleted.  If you need to build on top of it to correct
+   earlier mistakes, a new topic branch is created by forking at
+   the tip of the "master".  This is not strictly necessary, but
+   it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+   branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next".  Young
+    topic branches can have stupid mistakes you would rather
+    clean up before publishing, and things that have not been
+    merged into other branches can be easily rebased without
+    affecting other people.  But once it is published, you would
+    not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+    Then you can delete it.  More importantly, you should not
+    build on top of it -- other people may already want to
+    change things related to the topic as patches against your
+    "master", so if you need further changes, it is better to
+    fork the topic (perhaps with the same name) afresh from the
+    tip of "master".
+
+Let's look at this example:
+
+		   o---o---o---o---o---o---o---o---o---o "next"
+		  /       /           /           /
+		 /   a---a---b A     /           /
+		/   /               /           /
+	       /   /   c---c---c---c B         /
+	      /   /   /             \         /
+	     /   /   /   b---b C     \       /
+	    /   /   /   /             \     /
+    ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished.  It has been fully merged up to "master" and "next",
+   and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+	git rev-list ^master ^topic next
+	git rev-list ^master        next
+
+	if these match, topic has not merged in next at all.
+
+To compute (2):
+
+	git rev-list master..topic
+
+	if this is empty, it is fully merged to "master".
diff -urP oauth2-1.1.3/.git/hooks/update.sample python-oauth2/.git/hooks/update.sample
--- oauth2-1.1.3/.git/hooks/update.sample	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/hooks/update.sample	2010-10-22 13:09:06.817317000 -0400
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# An example hook script to blocks unannotated tags from entering.
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, rename this file to "update".
+#
+# Config
+# ------
+# hooks.allowunannotated
+#   This boolean sets whether unannotated tags will be allowed into the
+#   repository.  By default they won't be.
+# hooks.allowdeletetag
+#   This boolean sets whether deleting tags will be allowed in the
+#   repository.  By default they won't be.
+# hooks.allowmodifytag
+#   This boolean sets whether a tag may be modified after creation. By default
+#   it won't be.
+# hooks.allowdeletebranch
+#   This boolean sets whether deleting branches will be allowed in the
+#   repository.  By default they won't be.
+# hooks.denycreatebranch
+#   This boolean sets whether remotely creating branches will be denied
+#   in the repository.  By default this is allowed.
+#
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+	echo "Don't run this script from the command line." >&2
+	echo " (if you want, you could supply GIT_DIR then run" >&2
+	echo "  $0 <ref> <oldrev> <newrev>)" >&2
+	exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+	echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+	exit 1
+fi
+
+# --- Config
+allowunannotated=$(git config --bool hooks.allowunannotated)
+allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --bool hooks.denycreatebranch)
+allowdeletetag=$(git config --bool hooks.allowdeletetag)
+allowmodifytag=$(git config --bool hooks.allowmodifytag)
+
+# check for no description
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+case "$projectdesc" in
+"Unnamed repository"* | "")
+	echo "*** Project description file hasn't been set" >&2
+	exit 1
+	;;
+esac
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
+	newrev_type=delete
+else
+	newrev_type=$(git cat-file -t $newrev)
+fi
+
+case "$refname","$newrev_type" in
+	refs/tags/*,commit)
+		# un-annotated tag
+		short_refname=${refname##refs/tags/}
+		if [ "$allowunannotated" != "true" ]; then
+			echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
+			echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+			exit 1
+		fi
+		;;
+	refs/tags/*,delete)
+		# delete tag
+		if [ "$allowdeletetag" != "true" ]; then
+			echo "*** Deleting a tag is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/tags/*,tag)
+		# annotated tag
+		if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+		then
+			echo "*** Tag '$refname' already exists." >&2
+			echo "*** Modifying a tag is not allowed in this repository." >&2
+			exit 1
+		fi
+		;;
+	refs/heads/*,commit)
+		# branch
+		if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+			echo "*** Creating a branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/heads/*,delete)
+		# delete branch
+		if [ "$allowdeletebranch" != "true" ]; then
+			echo "*** Deleting a branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	refs/remotes/*,commit)
+		# tracking branch
+		;;
+	refs/remotes/*,delete)
+		# delete tracking branch
+		if [ "$allowdeletebranch" != "true" ]; then
+			echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+			exit 1
+		fi
+		;;
+	*)
+		# Anything else (is there anything else?)
+		echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+		exit 1
+		;;
+esac
+
+# --- Finished
+exit 0
Binary files oauth2-1.1.3/.git/index and python-oauth2/.git/index differ
diff -urP oauth2-1.1.3/.git/info/exclude python-oauth2/.git/info/exclude
--- oauth2-1.1.3/.git/info/exclude	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/info/exclude	2010-10-22 13:09:06.816317000 -0400
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff -urP oauth2-1.1.3/.git/logs/HEAD python-oauth2/.git/logs/HEAD
--- oauth2-1.1.3/.git/logs/HEAD	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/logs/HEAD	2010-10-22 13:09:08.119317001 -0400
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 1f9640f8467a90fcc20be6bbb8909d21bad308f6 Tom "spot" Callaway <tcallawa@redhat.com> 1287767348 -0400	clone: from http://github.com/simplegeo/python-oauth2.git
diff -urP oauth2-1.1.3/.git/logs/refs/heads/master python-oauth2/.git/logs/refs/heads/master
--- oauth2-1.1.3/.git/logs/refs/heads/master	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/logs/refs/heads/master	2010-10-22 13:09:08.118317001 -0400
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 1f9640f8467a90fcc20be6bbb8909d21bad308f6 Tom "spot" Callaway <tcallawa@redhat.com> 1287767348 -0400	clone: from http://github.com/simplegeo/python-oauth2.git
Binary files oauth2-1.1.3/.git/objects/pack/pack-42a49ad7443741e2064e6a6906d72b3c683dbdbc.idx and python-oauth2/.git/objects/pack/pack-42a49ad7443741e2064e6a6906d72b3c683dbdbc.idx differ
Binary files oauth2-1.1.3/.git/objects/pack/pack-42a49ad7443741e2064e6a6906d72b3c683dbdbc.pack and python-oauth2/.git/objects/pack/pack-42a49ad7443741e2064e6a6906d72b3c683dbdbc.pack differ
diff -urP oauth2-1.1.3/.git/packed-refs python-oauth2/.git/packed-refs
--- oauth2-1.1.3/.git/packed-refs	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/packed-refs	2010-10-22 13:09:08.076317000 -0400
@@ -0,0 +1,16 @@
+# pack-refs with: peeled 
+ced0c3f1407fa997e7377e9c55b328f42f3429a0 refs/tags/pre-1.0a
+d8cdf31bb0a8c020ab8823090e4441e78f5a5798 refs/tags/1.2.1
+^1f9640f8467a90fcc20be6bbb8909d21bad308f6
+bac0c0ca3895198e508278aa638abf62c7a600f4 refs/tags/1.2.0
+9962d31cc78806b2b17cd3dcd2c7bdc8cc0085c0 refs/tags/1.1.3
+340584ea5e82febb218461d51db2a95a17814e17 refs/tags/1.0.7
+31f89f0cee7b3f21cc641f7f3e6df6cbd800d06a refs/tags/1.0.6
+c040abcb92954275b7fd320bedac1f26dd495d42 refs/tags/1.0.5
+bff534876aeb2aca93e414f485fbc850fab64810 refs/tags/1.0.4
+bff534876aeb2aca93e414f485fbc850fab64810 refs/tags/1.0.3
+bde974cc5e1224d36c5d46086e34aee7dac1cdd7 refs/tags/1.0.2
+39ebd39bddec4111b7725b9887db7352b69780ef refs/tags/1.0.1
+c3cab5a4c0cae81c1a0842ad89944b6bbc81afd8 refs/tags/1.0.0
+^905680d424c21821e02684579d5eb982fed2f85d
+1f9640f8467a90fcc20be6bbb8909d21bad308f6 refs/remotes/origin/master
diff -urP oauth2-1.1.3/.git/refs/heads/master python-oauth2/.git/refs/heads/master
--- oauth2-1.1.3/.git/refs/heads/master	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/refs/heads/master	2010-10-22 13:09:08.118317001 -0400
@@ -0,0 +1 @@
+1f9640f8467a90fcc20be6bbb8909d21bad308f6
diff -urP oauth2-1.1.3/.git/refs/remotes/origin/HEAD python-oauth2/.git/refs/remotes/origin/HEAD
--- oauth2-1.1.3/.git/refs/remotes/origin/HEAD	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.git/refs/remotes/origin/HEAD	2010-10-22 13:09:08.117316999 -0400
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff -urP oauth2-1.1.3/.gitignore python-oauth2/.gitignore
--- oauth2-1.1.3/.gitignore	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/.gitignore	2010-10-22 13:09:08.122317001 -0400
@@ -0,0 +1,3 @@
+*.py?
+*.egg-info
+*.swp
diff -urP oauth2-1.1.3/LICENSE.txt python-oauth2/LICENSE.txt
--- oauth2-1.1.3/LICENSE.txt	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/LICENSE.txt	2010-10-22 13:09:08.122317001 -0400
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2007 Leah Culver
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff -urP oauth2-1.1.3/Makefile python-oauth2/Makefile
--- oauth2-1.1.3/Makefile	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/Makefile	2010-10-22 13:09:08.122317001 -0400
@@ -0,0 +1,155 @@
+PYTHON      = $(shell test -x bin/python && echo bin/python || \
+                      echo `which python`)
+PYVERS      = $(shell $(PYTHON) -c 'import sys; print "%s.%s" % sys.version_info[0:2]')
+VIRTUALENV  = $(shell /bin/echo -n `which virtualenv || \
+                                    which virtualenv-$(PYVERS) || \
+                                    which virtualenv$(PYVERS)`)
+VIRTUALENV += --no-site-packages
+PAGER      ?= less
+DEPS       := $(shell find $(PWD)/deps -type f -printf "file://%p ")
+COVERAGE    = $(shell test -x bin/coverage && echo bin/coverage || echo true)
+SETUP       = $(PYTHON) ./setup.py
+EZ_INSTALL  = $(SETUP) easy_install -f "$(DEPS)"
+PYLINT      = bin/pylint
+PLATFORM    = $(shell $(PYTHON) -c "from pkg_resources import get_build_platform; print get_build_platform()")
+OS         := $(shell uname)
+EGG        := $(shell $(SETUP) --fullname)-py$(PYVERS).egg
+SDIST      := $(shell $(SETUP) --fullname).tar.gs
+SRCDIR     := oauth2
+SOURCES    := $(shell find $(SRCDIR) -type f -name \*.py -not -name 'test_*')
+TESTS      := $(shell find $(SRCDIR) -type f -name test_\*.py)
+COVERED    := $(SOURCES)
+ROOT        = $(shell pwd)
+ROOTCMD     = fakeroot
+SIGN_KEY   ?= nerds@simplegeo.com
+BUILD_NUMBER ?= 1
+
+
+.PHONY: test dev clean extraclean debian/changelog
+
+all: egg
+egg: dist/$(EGG)
+
+dist/$(EGG):
+	$(SETUP) bdist_egg
+
+sdist:
+	$(SETUP) sdist
+
+debian/changelog:
+	-git branch -D changelog
+	git checkout -b changelog
+	git-dch -a -N $(shell $(SETUP) --version) --debian-branch changelog \
+            --snapshot --snapshot-number=$(BUILD_NUMBER)
+
+deb: debian/changelog
+	test -d dist/deb || mkdir -p dist/deb
+	dpkg-buildpackage -r$(ROOTCMD) -k$(SIGN_KEY)
+	mv ../python-oauth2_* dist/deb
+
+test:
+	$(SETUP) test --with-coverage --cover-package=oauth2
+
+sdist:
+	python setup.py sdist
+
+xunit.xml: bin/nosetests $(SOURCES) $(TESTS)
+	$(SETUP) test --with-xunit --xunit-file=$@
+
+bin/nosetests: bin/easy_install
+	@$(EZ_INSTALL) nose
+
+coverage: .coverage
+	@$(COVERAGE) html -d $@ $(COVERED)
+
+coverage.xml: .coverage
+	@$(COVERAGE) xml $(COVERED)
+
+.coverage: $(SOURCES) $(TESTS) bin/coverage bin/nosetests
+	-@$(COVERAGE) run $(SETUP) test
+
+bin/coverage: bin/easy_install
+	@$(EZ_INSTALL) coverage
+
+profile: .profile bin/pyprof2html
+	bin/pyprof2html -o $@ $<
+
+.profile: $(SOURCES) bin/nosetests
+	-$(SETUP) test -q --with-profile --profile-stats-file=$@
+
+bin/pyprof2html: bin/easy_install bin/
+	@$(EZ_INSTALL) pyprof2html
+
+docs: $(SOURCES) bin/epydoc
+	@echo bin/epydoc -q --html --no-frames -o $@ ...
+	@bin/epydoc -q --html --no-frames -o $@ $(SOURCES)
+
+bin/epydoc: bin/easy_install
+	@$(EZ_INSTALL) epydoc
+
+bin/pep8: bin/easy_install
+	@$(EZ_INSTALL) pep8
+
+pep8: bin/pep8
+	@bin/pep8 --repeat --ignore E225 $(SRCDIR)
+
+pep8.txt: bin/pep8
+	@bin/pep8 --repeat --ignore E225 $(SRCDIR) > $@
+
+lint: bin/pylint
+	-$(PYLINT) -f colorized $(SRCDIR)
+
+lint.html: bin/pylint
+	-$(PYLINT) -f html $(SRCDIR) > $@
+
+lint.txt: bin/pylint
+	-$(PYLINT) -f parseable $(SRCDIR) > $@
+
+bin/pylint: bin/easy_install
+	@$(EZ_INSTALL) pylint
+
+README.html: README.mkd | bin/markdown
+	bin/markdown -e utf-8 $^ -f $@
+
+bin/markdown: bin/easy_install
+	@$(EZ_INSTALL) Markdown
+
+
+# Development setup
+rtfm:
+	$(PAGER) README.mkd
+
+tags: TAGS.gz
+
+TAGS.gz: TAGS
+	gzip $^
+
+TAGS: $(SOURCES)
+	ctags -eR .
+
+env: bin/easy_install
+
+bin/easy_install:
+	$(VIRTUALENV) .
+	-test -f deps/setuptools* && $@ -U deps/setuptools*
+
+dev: develop
+develop: env
+	nice -n 20 $(SETUP) develop
+	@echo "            ---------------------------------------------"
+	@echo "            To activate the development environment, run:"
+	@echo "                           . bin/activate"
+	@echo "            ---------------------------------------------"
+
+clean:
+clean:
+	find . -type f -name \*.pyc -exec rm {} \;
+	rm -rf build dist TAGS TAGS.gz digg.egg-info tmp .coverage \
+	       coverage coverage.xml docs lint.html lint.txt profile \
+	       .profile *.egg xunit.xml
+	@if test "$(OS)" = "Linux"; then $(ROOTCMD) debian/rules clean; fi
+
+
+xclean: extraclean
+extraclean: clean
+	rm -rf bin lib .Python include
diff -urP oauth2-1.1.3/oauth2/clients/imap.py python-oauth2/oauth2/clients/imap.py
--- oauth2-1.1.3/oauth2/clients/imap.py	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/oauth2/clients/imap.py	2010-10-22 13:09:08.125317001 -0400
@@ -0,0 +1,40 @@
+"""
+The MIT License
+
+Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import oauth2
+import imaplib
+
+
+class IMAP4_SSL(imaplib.IMAP4_SSL):
+    """IMAP wrapper for imaplib.IMAP4_SSL that implements XOAUTH."""
+
+    def authenticate(self, url, consumer, token):
+        if consumer is not None and not isinstance(consumer, oauth2.Consumer):
+            raise ValueError("Invalid consumer.")
+
+        if token is not None and not isinstance(token, oauth2.Token):
+            raise ValueError("Invalid token.")
+
+        imaplib.IMAP4_SSL.authenticate(self, 'XOAUTH',
+            lambda x: oauth2.build_xoauth_string(url, consumer, token))
diff -urP oauth2-1.1.3/oauth2/clients/smtp.py python-oauth2/oauth2/clients/smtp.py
--- oauth2-1.1.3/oauth2/clients/smtp.py	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/oauth2/clients/smtp.py	2010-10-22 13:09:08.125317001 -0400
@@ -0,0 +1,41 @@
+"""
+The MIT License
+
+Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+
+import oauth2
+import smtplib
+import base64
+
+
+class SMTP(smtplib.SMTP):
+    """SMTP wrapper for smtplib.SMTP that implements XOAUTH."""
+
+    def authenticate(self, url, consumer, token):
+        if consumer is not None and not isinstance(consumer, oauth2.Consumer):
+            raise ValueError("Invalid consumer.")
+
+        if token is not None and not isinstance(token, oauth2.Token):
+            raise ValueError("Invalid token.")
+
+        self.docmd('AUTH', 'XOAUTH %s' % \
+            base64.b64encode(oauth2.build_xoauth_string(url, consumer, token)))
diff -urP oauth2-1.1.3/oauth2/__init__.py python-oauth2/oauth2/__init__.py
--- oauth2-1.1.3/oauth2/__init__.py	2010-03-27 14:17:54.000000000 -0400
+++ python-oauth2/oauth2/__init__.py	2010-10-22 13:09:08.125317001 -0400
@@ -1,7 +1,7 @@
 """
 The MIT License
 
-Copyright (c) 2007 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
+Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
@@ -36,7 +36,7 @@
     from cgi import parse_qs, parse_qsl
 
 
-VERSION = '1.0' # Hi Blaine!
+VERSION = '1.0'  # Hi Blaine!
 HTTP_METHOD = 'GET'
 SIGNATURE_METHOD = 'PLAINTEXT'
 
@@ -44,7 +44,7 @@
 class Error(RuntimeError):
     """Generic exception class."""
 
-    def __init__(self, message='OAuth error occured.'):
+    def __init__(self, message='OAuth error occurred.'):
         self._message = message
 
     @property
@@ -55,14 +55,32 @@
     def __str__(self):
         return self._message
 
+
 class MissingSignature(Error):
     pass
 
+
 def build_authenticate_header(realm=''):
     """Optional WWW-Authenticate header (401 error)"""
     return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
 
 
+def build_xoauth_string(url, consumer, token=None):
+    """Build an XOAUTH string for use in SMTP/IMPA authentication."""
+    request = Request.from_consumer_and_token(consumer, token,
+        "GET", url)
+
+    signing_method = SignatureMethod_HMAC_SHA1()
+    request.sign_request(signing_method, consumer, token)
+
+    params = []
+    for k, v in sorted(request.iteritems()):
+        if v is not None:
+            params.append('%s="%s"' % (k, escape(v)))
+
+    return "%s %s %s" % ("GET", url, ','.join(params))
+
+
 def escape(s):
     """Escape a URL including any /."""
     return urllib.quote(s, safe='~')
@@ -114,10 +132,8 @@
             raise ValueError("Key and secret must be set.")
 
     def __str__(self):
-        data = {
-            'oauth_consumer_key': self.key,
-            'oauth_consumer_secret': self.secret
-        }
+        data = {'oauth_consumer_key': self.key,
+            'oauth_consumer_secret': self.secret}
 
         return urllib.urlencode(data)
 
@@ -216,7 +232,7 @@
         try:
             token.callback_confirmed = params['oauth_callback_confirmed'][0]
         except KeyError:
-            pass # 1.0, no callback confirmed.
+            pass  # 1.0, no callback confirmed.
         return token
 
     def __str__(self):
@@ -309,16 +325,36 @@
         # tell urlencode to deal with sequence values and map them correctly
         # to resulting querystring. for example self["k"] = ["v1", "v2"] will
         # result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D
-        return urllib.urlencode(self, True)
+        return urllib.urlencode(self, True).replace('+', '%20')
  
     def to_url(self):
         """Serialize as a URL for a GET request."""
         base_url = urlparse.urlparse(self.url)
-        query = parse_qs(base_url.query)
+        try:
+            query = base_url.query
+        except AttributeError:
+            # must be python <2.5
+            query = base_url[4]
+        query = parse_qs(query)
         for k, v in self.items():
             query.setdefault(k, []).append(v)
-        url = (base_url.scheme, base_url.netloc, base_url.path, base_url.params,
-               urllib.urlencode(query, True), base_url.fragment)
+        
+        try:
+            scheme = base_url.scheme
+            netloc = base_url.netloc
+            path = base_url.path
+            params = base_url.params
+            fragment = base_url.fragment
+        except AttributeError:
+            # must be python <2.5
+            scheme = base_url[0]
+            netloc = base_url[1]
+            path = base_url[2]
+            params = base_url[3]
+            fragment = base_url[5]
+        
+        url = (scheme, netloc, path, params,
+               urllib.urlencode(query, True), fragment)
         return urlparse.urlunparse(url)
 
     def get_parameter(self, parameter):
@@ -343,14 +379,17 @@
 
         # Include any query string parameters from the provided URL
         query = urlparse.urlparse(self.url)[4]
-        items.extend(self._split_url_string(query).items())
+        
+        url_items = self._split_url_string(query).items()
+        non_oauth_url_items = list([(k, v) for k, v in url_items  if not k.startswith('oauth_')])
+        items.extend(non_oauth_url_items)
 
         encoded_str = urllib.urlencode(sorted(items))
         # Encode signature parameters per Oauth Core 1.0 protocol
         # spec draft 7, section 3.6
         # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6)
         # Spaces must be encoded with "%20" instead of "+"
-        return encoded_str.replace('+', '%20')
+        return encoded_str.replace('+', '%20').replace('%7E', '~')
  
     def sign_request(self, signature_method, consumer, token):
         """Set the signature parameter to the result of sign."""
@@ -473,6 +512,69 @@
         return parameters
 
 
+class Client(httplib2.Http):
+    """OAuthClient is a worker to attempt to execute a request."""
+
+    def __init__(self, consumer, token=None, cache=None, timeout=None,
+        proxy_info=None):
+
+        if consumer is not None and not isinstance(consumer, Consumer):
+            raise ValueError("Invalid consumer.")
+
+        if token is not None and not isinstance(token, Token):
+            raise ValueError("Invalid token.")
+
+        self.consumer = consumer
+        self.token = token
+        self.method = SignatureMethod_HMAC_SHA1()
+
+        httplib2.Http.__init__(self, cache=cache, timeout=timeout, 
+            proxy_info=proxy_info)
+
+    def set_signature_method(self, method):
+        if not isinstance(method, SignatureMethod):
+            raise ValueError("Invalid signature method.")
+
+        self.method = method
+
+    def request(self, uri, method="GET", body=None, headers=None, 
+        redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
+        DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded'
+
+        if not isinstance(headers, dict):
+            headers = {}
+
+        is_multipart = method == 'POST' and headers.get('Content-Type', 
+            DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE
+
+        if body and method == "POST" and not is_multipart:
+            parameters = dict(parse_qsl(body))
+        else:
+            parameters = None
+
+        req = Request.from_consumer_and_token(self.consumer, 
+            token=self.token, http_method=method, http_url=uri, 
+            parameters=parameters)
+
+        req.sign_request(self.method, self.consumer, self.token)
+
+        if method == "POST":
+            headers['Content-Type'] = headers.get('Content-Type', 
+                DEFAULT_CONTENT_TYPE)
+            if is_multipart:
+                headers.update(req.to_header())
+            else:
+                body = req.to_postdata()
+        elif method == "GET":
+            uri = req.to_url()
+        else:
+            headers.update(req.to_header())
+
+        return httplib2.Http.request(self, uri, method=method, body=body, 
+            headers=headers, redirections=redirections, 
+            connection_type=connection_type)
+
+
 class Server(object):
     """A skeletal implementation of a service provider, providing protected
     resources to requests from authorized consumers.
@@ -564,68 +666,8 @@
         lapsed = now - timestamp
         if lapsed > self.timestamp_threshold:
             raise Error('Expired timestamp: given %d and now %s has a '
-                'greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
-
-
-class Client(httplib2.Http):
-    """OAuthClient is a worker to attempt to execute a request."""
-
-    def __init__(self, consumer, token=None, cache=None, timeout=None,
-        proxy_info=None):
-
-        if consumer is not None and not isinstance(consumer, Consumer):
-            raise ValueError("Invalid consumer.")
-
-        if token is not None and not isinstance(token, Token):
-            raise ValueError("Invalid token.")
-
-        self.consumer = consumer
-        self.token = token
-        self.method = SignatureMethod_HMAC_SHA1()
-
-        httplib2.Http.__init__(self, cache=cache, timeout=timeout, 
-            proxy_info=proxy_info)
-
-    def set_signature_method(self, method):
-        if not isinstance(method, SignatureMethod):
-            raise ValueError("Invalid signature method.")
-
-        self.method = method
-
-    def request(self, uri, method="GET", body=None, headers=None, 
-        redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
-        DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded'
-
-        if not isinstance(headers, dict):
-            headers = {}
-
-        is_multipart = method == 'POST' and headers.get('Content-Type', DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE
-
-        if body and method == "POST" and not is_multipart:
-            parameters = dict(parse_qsl(body))
-        else:
-            parameters = None
-
-        req = Request.from_consumer_and_token(self.consumer, token=self.token,
-            http_method=method, http_url=uri, parameters=parameters)
-
-        req.sign_request(self.method, self.consumer, self.token)
-
-
-        if method == "POST":
-            headers['Content-Type'] = headers.get('Content-Type', DEFAULT_CONTENT_TYPE)
-            if is_multipart:
-                headers.update(req.to_header())
-            else:
-                body = req.to_postdata()
-        elif method == "GET":
-            uri = req.to_url()
-        else:
-            headers.update(req.to_header())
-
-        return httplib2.Http.request(self, uri, method=method, body=body, 
-            headers=headers, redirections=redirections, 
-            connection_type=connection_type)
+                'greater difference than threshold %d' % (timestamp, now, 
+                    self.timestamp_threshold))
 
 
 class SignatureMethod(object):
@@ -668,6 +710,9 @@
     name = 'HMAC-SHA1'
         
     def signing_base(self, request, consumer, token):
+        if request.normalized_url is None:
+            raise ValueError("Base URL for request is not set.")
+
         sig = (
             escape(request.method),
             escape(request.normalized_url),
@@ -695,6 +740,7 @@
         # Calculate the digest base 64.
         return binascii.b2a_base64(hashed.digest())[:-1]
 
+
 class SignatureMethod_PLAINTEXT(SignatureMethod):
 
     name = 'PLAINTEXT'
@@ -710,4 +756,3 @@
     def sign(self, request, consumer, token):
         key, raw = self.signing_base(request, consumer, token)
         return raw
-
Only in oauth2-1.1.3/: oauth2.egg-info
Only in oauth2-1.1.3/: PKG-INFO
diff -urP oauth2-1.1.3/README.md python-oauth2/README.md
--- oauth2-1.1.3/README.md	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/README.md	2010-10-22 13:09:08.122317001 -0400
@@ -0,0 +1,357 @@
+# Overview
+
+This code was originally forked from [Leah Culver and Andy Smith's oauth.py code](http://github.com/leah/python-oauth/). Some of the tests come from a [fork by Vic Fryzel](http://github.com/shellsage/python-oauth), while a revamped Request class and more tests were merged in from [Mark Paschal's fork](http://github.com/markpasc/python-oauth). A number of notable differences exist between this code and its forefathers:
+
+* 100% unit test coverage.
+* The <code>DataStore</code> object has been completely ripped out. While creating unit tests for the library I found several substantial bugs with the implementation and confirmed with Andy Smith that it was never fully baked.
+* Classes are no longer prefixed with <code>OAuth</code>.
+* The <code>Request</code> class now extends from <code>dict</code>.
+* The library is likely no longer compatible with Python 2.3.
+* The <code>Client</code> class works and extends from <code>httplib2</code>. It's a thin wrapper that handles automatically signing any normal HTTP request you might wish to make.
+
+# Signing a Request
+
+    import oauth2 as oauth
+    import time
+    
+    # Set the API endpoint 
+    url = "http://example.com/photos"
+    
+    # Set the base oauth_* parameters along with any other parameters required
+    # for the API call.
+    params = {
+        'oauth_version': "1.0",
+        'oauth_nonce': oauth.generate_nonce(),
+        'oauth_timestamp': int(time.time())
+        'user': 'joestump',
+        'photoid': 555555555555
+    }
+    
+    # Set up instances of our Token and Consumer. The Consumer.key and 
+    # Consumer.secret are given to you by the API provider. The Token.key and
+    # Token.secret is given to you after a three-legged authentication.
+    token = oauth.Token(key="tok-test-key", secret="tok-test-secret")
+    consumer = oauth.Consumer(key="con-test-key", secret="con-test-secret")
+    
+    # Set our token/key parameters
+    params['oauth_token'] = token.key
+    params['oauth_consumer_key'] = consumer.key
+    
+    # Create our request. Change method, etc. accordingly.
+    req = oauth.Request(method="GET", url=url, parameters=params)
+    
+    # Sign the request.
+    signature_method = oauth.SignatureMethod_HMAC_SHA1()
+    req.sign_request(signature_method, consumer, token)
+
+# Using the Client
+
+The <code>oauth2.Client</code> is based on <code>httplib2</code> and works just as you'd expect it to. The only difference is the first two arguments to the constructor are an instance of <code>oauth2.Consumer</code> and <code>oauth2.Token</code> (<code>oauth2.Token</code> is only needed for three-legged requests).
+
+    import oauth2 as oauth
+    
+    # Create your consumer with the proper key/secret.
+    consumer = oauth.Consumer(key="your-twitter-consumer-key", 
+        secret="your-twitter-consumer-secret")
+    
+    # Request token URL for Twitter.
+    request_token_url = "http://twitter.com/oauth/request_token"
+    
+    # Create our client.
+    client = oauth.Client(consumer)
+    
+    # The OAuth Client request works just like httplib2 for the most part.
+    resp, content = client.request(request_token_url, "GET")
+    print resp
+    print content
+
+# Twitter Three-legged OAuth Example
+
+Below is an example of how one would go through a three-legged OAuth flow to
+gain access to protected resources on Twitter. This is a simple CLI script, but
+can be easily translated to a web application.
+
+    import urlparse
+    import oauth2 as oauth
+    
+    consumer_key = 'my_key_from_twitter'
+    consumer_secret = 'my_secret_from_twitter'
+    
+    request_token_url = 'http://twitter.com/oauth/request_token'
+    access_token_url = 'http://twitter.com/oauth/access_token'
+    authorize_url = 'http://twitter.com/oauth/authorize'
+    
+    consumer = oauth.Consumer(consumer_key, consumer_secret)
+    client = oauth.Client(consumer)
+    
+    # Step 1: Get a request token. This is a temporary token that is used for 
+    # having the user authorize an access token and to sign the request to obtain 
+    # said access token.
+    
+    resp, content = client.request(request_token_url, "GET")
+    if resp['status'] != '200':
+        raise Exception("Invalid response %s." % resp['status'])
+    
+    request_token = dict(urlparse.parse_qsl(content))
+    
+    print "Request Token:"
+    print "    - oauth_token        = %s" % request_token['oauth_token']
+    print "    - oauth_token_secret = %s" % request_token['oauth_token_secret']
+    print 
+    
+    # Step 2: Redirect to the provider. Since this is a CLI script we do not 
+    # redirect. In a web application you would redirect the user to the URL
+    # below.
+    
+    print "Go to the following link in your browser:"
+    print "%s?oauth_token=%s" % (authorize_url, request_token['oauth_token'])
+    print 
+    
+    # After the user has granted access to you, the consumer, the provider will
+    # redirect you to whatever URL you have told them to redirect to. You can 
+    # usually define this in the oauth_callback argument as well.
+    accepted = 'n'
+    while accepted.lower() == 'n':
+        accepted = raw_input('Have you authorized me? (y/n) ')
+    oauth_verifier = raw_input('What is the PIN? ')
+    
+    # Step 3: Once the consumer has redirected the user back to the oauth_callback
+    # URL you can request the access token the user has approved. You use the 
+    # request token to sign this request. After this is done you throw away the
+    # request token and use the access token returned. You should store this 
+    # access token somewhere safe, like a database, for future use.
+    token = oauth.Token(request_token['oauth_token'],
+        request_token['oauth_token_secret'])
+    token.set_verifier(oauth_verifier)
+    client = oauth.Client(consumer, token)
+    
+    resp, content = client.request(access_token_url, "POST")
+    access_token = dict(urlparse.parse_qsl(content))
+    
+    print "Access Token:"
+    print "    - oauth_token        = %s" % access_token['oauth_token']
+    print "    - oauth_token_secret = %s" % access_token['oauth_token_secret']
+    print
+    print "You may now access protected resources using the access tokens above." 
+    print
+
+# Logging into Django w/ Twitter
+
+Twitter also has the ability to authenticate a user [via an OAuth flow](http://apiwiki.twitter.com/Sign-in-with-Twitter). This
+flow is exactly like the three-legged OAuth flow, except you send them to a 
+slightly different URL to authorize them. 
+
+In this example we'll look at how you can implement this login flow using 
+Django and python-oauth2. 
+
+## Set up a Profile model
+
+You'll need a place to store all of your Twitter OAuth credentials after the
+user has logged in. In your app's `models.py` file you should add something
+that resembles the following model.
+
+    class Profile(models.Model):
+        user = models.ForeignKey(User)
+        oauth_token = models.CharField(max_length=200)
+        oauth_secret = models.CharField(max_length=200)
+
+## Set up your Django views
+
+### `urls.py`
+
+Your `urls.py` should look something like the following. Basically, you need to
+have a login URL, a callback URL that Twitter will redirect your users back to,
+and a logout URL.
+
+In this example `^login/` and `twitter_login` will send the user to Twitter to
+be logged in, `^login/authenticated/` and `twitter_authenticated` will confirm
+the login, create the account if necessary, and log the user into the 
+application, and `^logout`/ logs the user out in the `twitter_logout` view.
+
+
+    from django.conf.urls.defaults import *
+    from django.contrib import admin
+    from mytwitterapp.views import twitter_login, twitter_logout, \
+        twitter_authenticated
+
+    admin.autodiscover()
+
+    urlpatterns = patterns('',
+        url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+        url(r'^admin/', include(admin.site.urls)),
+        url(r'^login/?$', twitter_login),
+        url(r'^logout/?$', twitter_logout),
+        url(r'^login/authenticated/?$', twitter_authenticated),
+    )
+
+### `views.py`
+
+*NOTE:* The following code was coded for Python 2.4 so some of the libraries 
+and code here might need to be updated if you are using Python 2.6+. 
+
+    # Python
+    import oauth2 as oauth
+    import cgi
+
+    # Django
+    from django.shortcuts import render_to_response
+    from django.http import HttpResponseRedirect
+    from django.conf import settings
+    from django.contrib.auth import authenticate, login, logout
+    from django.contrib.auth.models import User
+    from django.contrib.auth.decorators import login_required
+
+    # Project
+    from mytwitterapp.models import Profile
+
+    # It's probably a good idea to put your consumer's OAuth token and
+    # OAuth secret into your project's settings. 
+    consumer = oauth.Consumer(settings.TWITTER_TOKEN, settings.TWITTER_SECRET)
+    client = oauth.Client(consumer)
+
+    request_token_url = 'http://twitter.com/oauth/request_token'
+    access_token_url = 'http://twitter.com/oauth/access_token'
+
+    # This is the slightly different URL used to authenticate/authorize.
+    authenticate_url = 'http://twitter.com/oauth/authenticate'
+
+    def twitter_login(request):
+        # Step 1. Get a request token from Twitter.
+        resp, content = client.request(request_token_url, "GET")
+        if resp['status'] != '200':
+            raise Exception("Invalid response from Twitter.")
+
+        # Step 2. Store the request token in a session for later use.
+        request.session['request_token'] = dict(cgi.parse_qsl(content))
+
+        # Step 3. Redirect the user to the authentication URL.
+        url = "%s?oauth_token=%s" % (authenticate_url,
+            request.session['request_token']['oauth_token'])
+
+        return HttpResponseRedirect(url)
+
+    
+    @login_required
+    def twitter_logout(request):
+        # Log a user out using Django's logout function and redirect them
+        # back to the homepage.
+        logout(request)
+        return HttpResponseRedirect('/')
+
+    def twitter_authenticated(request):
+        # Step 1. Use the request token in the session to build a new client.
+        token = oauth.Token(request.session['request_token']['oauth_token'],
+            request.session['request_token']['oauth_token_secret'])
+        client = oauth.Client(consumer, token)
+    
+        # Step 2. Request the authorized access token from Twitter.
+        resp, content = client.request(access_token_url, "GET")
+        if resp['status'] != '200':
+            print content
+            raise Exception("Invalid response from Twitter.")
+    
+        """
+        This is what you'll get back from Twitter. Note that it includes the
+        user's user_id and screen_name.
+        {
+            'oauth_token_secret': 'IcJXPiJh8be3BjDWW50uCY31chyhsMHEhqJVsphC3M',
+            'user_id': '120889797', 
+            'oauth_token': '120889797-H5zNnM3qE0iFoTTpNEHIz3noL9FKzXiOxwtnyVOD',
+            'screen_name': 'heyismysiteup'
+        }
+        """
+        access_token = dict(cgi.parse_qsl(content))
+    
+        # Step 3. Lookup the user or create them if they don't exist.
+        try:
+            user = User.objects.get(username=access_token['screen_name'])
+        except User.DoesNotExist:
+            # When creating the user I just use their screen_name@twitter.com
+            # for their email and the oauth_token_secret for their password.
+            # These two things will likely never be used. Alternatively, you 
+            # can prompt them for their email here. Either way, the password 
+            # should never be used.
+            user = User.objects.create_user(access_token['screen_name'],
+                '%s@twitter.com' % access_token['screen_name'],
+                access_token['oauth_token_secret'])
+    
+            # Save our permanent token and secret for later.
+            profile = Profile()
+            profile.user = user
+            profile.oauth_token = access_token['oauth_token']
+            profile.oauth_secret = access_token['oauth_token_secret']
+            profile.save()
+    
+        # Authenticate the user and log them in using Django's pre-built 
+        # functions for these things.
+        user = authenticate(username=access_token['screen_name'],
+            password=access_token['oauth_token_secret'])
+        login(request, user)
+    
+        return HttpResponseRedirect('/')
+    
+
+### `settings.py`
+
+* You'll likely want to set `LOGIN_URL` to `/login/` so that users are properly redirected to your Twitter login handler when you use `@login_required` in other parts of your Django app.
+* You can also set `AUTH_PROFILE_MODULE = 'mytwitterapp.Profile'` so that you can easily access the Twitter OAuth token/secret for that user using the `User.get_profile()` method in Django.
+
+# XOAUTH for IMAP and SMTP
+
+Gmail supports OAuth over IMAP and SMTP via a standard they call XOAUTH. This allows you to authenticate against Gmail's IMAP and SMTP servers using an OAuth token and secret. It also has the added benefit of allowing you to use vanilla SMTP and IMAP libraries. The `python-oauth2` package provides both IMAP and SMTP libraries that implement XOAUTH and wrap `imaplib.IMAP4_SSL` and `smtplib.SMTP`. This allows you to connect to Gmail with OAuth credentials using standard Python libraries. 
+
+## IMAP
+
+    import oauth2 as oauth
+    import oauth2.clients.imap as imaplib
+
+    # Set up your Consumer and Token as per usual. Just like any other
+    # three-legged OAuth request.
+    consumer = oauth.Consumer('your_consumer_key', 'your_consumer_secret')
+    token = oauth.Token('your_users_3_legged_token', 
+        'your_users_3_legged_token_secret')
+
+    # Setup the URL according to Google's XOAUTH implementation. Be sure
+    # to replace the email here with the appropriate email address that
+    # you wish to access.
+    url = "https://mail.google.com/mail/b/your_users_email@gmail.com/imap/"
+
+    conn = imaplib.IMAP4_SSL('imap.googlemail.com')
+    conn.debug = 4 
+
+    # This is the only thing in the API for impaplib.IMAP4_SSL that has 
+    # changed. You now authenticate with the URL, consumer, and token.
+    conn.authenticate(url, consumer, token)
+
+    # Once authenticated everything from the impalib.IMAP4_SSL class will 
+    # work as per usual without any modification to your code.
+    conn.select('INBOX')
+    print conn.list()
+
+
+## SMTP
+
+    import oauth2 as oauth
+    import oauth2.clients.smtp as smtplib
+
+    # Set up your Consumer and Token as per usual. Just like any other
+    # three-legged OAuth request.
+    consumer = oauth.Consumer('your_consumer_key', 'your_consumer_secret')
+    token = oauth.Token('your_users_3_legged_token', 
+        'your_users_3_legged_token_secret')
+
+    # Setup the URL according to Google's XOAUTH implementation. Be sure
+    # to replace the email here with the appropriate email address that
+    # you wish to access.
+    url = "https://mail.google.com/mail/b/your_users_email@gmail.com/smtp/"
+
+    conn = smtplib.SMTP('smtp.googlemail.com', 587)
+    conn.set_debuglevel(True)
+    conn.ehlo('test')
+    conn.starttls()
+
+    # Again the only thing modified from smtplib.SMTP is the authenticate
+    # method, which works identically to the imaplib.IMAP4_SSL method.
+    conn.authenticate(url, consumer, token)
+
+
diff -urP oauth2-1.1.3/setup.cfg python-oauth2/setup.cfg
--- oauth2-1.1.3/setup.cfg	2010-03-27 14:25:51.000000000 -0400
+++ python-oauth2/setup.cfg	2010-10-22 13:09:08.125317001 -0400
@@ -1,8 +1,2 @@
-[egg_info]
-tag_build = 
-tag_date = 0
-tag_svn_revision = 0
-
 [aliases]
 test = nosetests
-
diff -urP oauth2-1.1.3/setup.py python-oauth2/setup.py
--- oauth2-1.1.3/setup.py	2010-03-27 14:18:01.000000000 -0400
+++ python-oauth2/setup.py	2010-10-22 13:09:08.126317001 -0400
@@ -3,7 +3,7 @@
 from setuptools import setup, find_packages
 
 setup(name="oauth2",
-      version="1.1.3",
+      version="1.2.1",
       description="Library for OAuth version 1.0a.",
       author="Joe Stump",
       author_email="joe@simplegeo.com",
diff -urP oauth2-1.1.3/tests/test_oauth.py python-oauth2/tests/test_oauth.py
--- oauth2-1.1.3/tests/test_oauth.py	1969-12-31 19:00:00.000000000 -0500
+++ python-oauth2/tests/test_oauth.py	2010-10-22 13:09:08.126317001 -0400
@@ -0,0 +1,1034 @@
+"""
+The MIT License
+
+Copyright (c) 2009 Vic Fryzel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+"""
+import sys
+import os
+import unittest
+import oauth2 as oauth
+import random
+import time
+import urllib
+import urlparse
+from types import ListType
+import mox
+import httplib2
+
+# Fix for python2.5 compatibility
+try:
+    from urlparse import parse_qs, parse_qsl
+except ImportError:
+    from cgi import parse_qs, parse_qsl
+
+
+sys.path[0:0] = [os.path.join(os.path.dirname(__file__), ".."),]
+
+
+class TestError(unittest.TestCase):
+    def test_message(self):
+        try:
+            raise oauth.Error
+        except oauth.Error, e:
+            self.assertEqual(e.message, 'OAuth error occurred.')
+
+        msg = 'OMG THINGS BROKE!!!!'
+        try:
+            raise oauth.Error(msg)
+        except oauth.Error, e:
+            self.assertEqual(e.message, msg)
+
+    def test_str(self):
+        try:
+            raise oauth.Error
+        except oauth.Error, e:
+            self.assertEquals(str(e), 'OAuth error occurred.')
+
+class TestGenerateFunctions(unittest.TestCase):
+    def test_build_auth_header(self):
+        header = oauth.build_authenticate_header()
+        self.assertEqual(header['WWW-Authenticate'], 'OAuth realm=""')
+        self.assertEqual(len(header), 1)
+        realm = 'http://example.myrealm.com/'
+        header = oauth.build_authenticate_header(realm)
+        self.assertEqual(header['WWW-Authenticate'], 'OAuth realm="%s"' %
+                         realm)
+        self.assertEqual(len(header), 1)
+
+    def test_build_xoauth_string(self):
+        consumer = oauth.Consumer('consumer_token', 'consumer_secret')
+        token = oauth.Token('user_token', 'user_secret')
+        url = "https://mail.google.com/mail/b/joe@example.com/imap/"
+        xoauth_string = oauth.build_xoauth_string(url, consumer, token)
+
+        method, oauth_url, oauth_string = xoauth_string.split(' ')
+
+        self.assertEqual("GET", method)
+        self.assertEqual(url, oauth_url)
+
+        returned = {}
+        parts = oauth_string.split(',')
+        for part in parts:
+            var, val = part.split('=')
+            returned[var] = val.strip('"') 
+
+        self.assertEquals('HMAC-SHA1', returned['oauth_signature_method'])
+        self.assertEquals('user_token', returned['oauth_token'])
+        self.assertEquals('consumer_token', returned['oauth_consumer_key'])
+        self.assertTrue('oauth_signature' in returned, 'oauth_signature')
+
+    def test_escape(self):
+        string = 'http://whatever.com/~someuser/?test=test&other=other'
+        self.assert_('~' in oauth.escape(string))
+        string = '../../../../../../../etc/passwd'
+        self.assert_('../' not in oauth.escape(string))
+
+    def test_gen_nonce(self):
+        nonce = oauth.generate_nonce()
+        self.assertEqual(len(nonce), 8)
+        nonce = oauth.generate_nonce(20)
+        self.assertEqual(len(nonce), 20)
+
+    def test_gen_verifier(self):
+        verifier = oauth.generate_verifier()
+        self.assertEqual(len(verifier), 8)
+        verifier = oauth.generate_verifier(16)
+        self.assertEqual(len(verifier), 16)
+
+    def test_gen_timestamp(self):
+        exp = int(time.time())
+        now = oauth.generate_timestamp()
+        self.assertEqual(exp, now)
+
+class TestConsumer(unittest.TestCase):
+    def setUp(self):
+        self.key = 'my-key'
+        self.secret = 'my-secret'
+        self.consumer = oauth.Consumer(key=self.key, secret=self.secret)
+
+    def test_init(self):
+        self.assertEqual(self.consumer.key, self.key)
+        self.assertEqual(self.consumer.secret, self.secret)
+
+    def test_basic(self):
+        self.assertRaises(ValueError, lambda: oauth.Consumer(None, None))
+        self.assertRaises(ValueError, lambda: oauth.Consumer('asf', None))
+        self.assertRaises(ValueError, lambda: oauth.Consumer(None, 'dasf'))
+
+    def test_str(self):
+        res = dict(parse_qsl(str(self.consumer)))
+        self.assertTrue('oauth_consumer_key' in res)
+        self.assertTrue('oauth_consumer_secret' in res)
+        self.assertEquals(res['oauth_consumer_key'], self.consumer.key)
+        self.assertEquals(res['oauth_consumer_secret'], self.consumer.secret)
+
+class TestToken(unittest.TestCase):
+    def setUp(self):
+        self.key = 'my-key'
+        self.secret = 'my-secret'
+        self.token = oauth.Token(self.key, self.secret)
+
+    def test_basic(self):
+        self.assertRaises(ValueError, lambda: oauth.Token(None, None))
+        self.assertRaises(ValueError, lambda: oauth.Token('asf', None))
+        self.assertRaises(ValueError, lambda: oauth.Token(None, 'dasf'))
+
+    def test_init(self):
+        self.assertEqual(self.token.key, self.key)
+        self.assertEqual(self.token.secret, self.secret)
+        self.assertEqual(self.token.callback, None)
+        self.assertEqual(self.token.callback_confirmed, None)
+        self.assertEqual(self.token.verifier, None)
+
+    def test_set_callback(self):
+        self.assertEqual(self.token.callback, None)
+        self.assertEqual(self.token.callback_confirmed, None)
+        cb = 'http://www.example.com/my-callback'
+        self.token.set_callback(cb)
+        self.assertEqual(self.token.callback, cb)
+        self.assertEqual(self.token.callback_confirmed, 'true')
+        self.token.set_callback(None)
+        self.assertEqual(self.token.callback, None)
+        # TODO: The following test should probably not pass, but it does
+        #       To fix this, check for None and unset 'true' in set_callback
+        #       Additionally, should a confirmation truly be done of the callback?
+        self.assertEqual(self.token.callback_confirmed, 'true')
+
+    def test_set_verifier(self):
+        self.assertEqual(self.token.verifier, None)
+        v = oauth.generate_verifier()
+        self.token.set_verifier(v)
+        self.assertEqual(self.token.verifier, v)
+        self.token.set_verifier()
+        self.assertNotEqual(self.token.verifier, v)
+        self.token.set_verifier('')
+        self.assertEqual(self.token.verifier, '')
+
+    def test_get_callback_url(self):
+        self.assertEqual(self.token.get_callback_url(), None)
+
+        self.token.set_verifier()
+        self.assertEqual(self.token.get_callback_url(), None)
+
+        cb = 'http://www.example.com/my-callback?save=1&return=true'
+        v = oauth.generate_verifier()
+        self.token.set_callback(cb)
+        self.token.set_verifier(v)
+        url = self.token.get_callback_url()
+        verifier_str = '&oauth_verifier=%s' % v
+        self.assertEqual(url, '%s%s' % (cb, verifier_str))
+
+        cb = 'http://www.example.com/my-callback-no-query'
+        v = oauth.generate_verifier()
+        self.token.set_callback(cb)
+        self.token.set_verifier(v)
+        url = self.token.get_callback_url()
+        verifier_str = '?oauth_verifier=%s' % v
+        self.assertEqual(url, '%s%s' % (cb, verifier_str))
+
+    def test_to_string(self):
+        string = 'oauth_token_secret=%s&oauth_token=%s' % (self.secret,
+                                                           self.key)
+        self.assertEqual(self.token.to_string(), string)
+
+        self.token.set_callback('http://www.example.com/my-callback')
+        string += '&oauth_callback_confirmed=true'
+        self.assertEqual(self.token.to_string(), string)
+
+    def _compare_tokens(self, new):
+        self.assertEqual(self.token.key, new.key)
+        self.assertEqual(self.token.secret, new.secret)
+        # TODO: What about copying the callback to the new token?
+        # self.assertEqual(self.token.callback, new.callback)
+        self.assertEqual(self.token.callback_confirmed,
+                         new.callback_confirmed)
+        # TODO: What about copying the verifier to the new token?
+        # self.assertEqual(self.token.verifier, new.verifier)
+
+    def test_to_string(self):
+        tok = oauth.Token('tooken', 'seecret')
+        self.assertEqual(str(tok), 'oauth_token_secret=seecret&oauth_token=tooken')
+
+    def test_from_string(self):
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string(''))
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('blahblahblah'))
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('blah=blah'))
+
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token_secret=asfdasf'))
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token_secret='))
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token=asfdasf'))
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token='))
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token=&oauth_token_secret='))
+        self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token=tooken%26oauth_token_secret=seecret'))
+
+        string = self.token.to_string()
+        new = oauth.Token.from_string(string)
+        self._compare_tokens(new)
+
+        self.token.set_callback('http://www.example.com/my-callback')
+        string = self.token.to_string()
+        new = oauth.Token.from_string(string)
+        self._compare_tokens(new)
+
+class TestRequest(unittest.TestCase):
+    def test_setter(self):
+        url = "http://example.com"
+        method = "GET"
+        req = oauth.Request(method)
+        self.assertTrue(req.url is None)
+        self.assertTrue(req.normalized_url is None)
+
+    def test_deleter(self):
+        url = "http://example.com"
+        method = "GET"
+        req = oauth.Request(method, url)
+
+        try:
+            del req.url
+            url = req.url
+            self.fail("AttributeError should have been raised on empty url.")
+        except AttributeError:
+            pass
+        except Exception, e:
+            self.fail(str(e))
+
+    def test_url(self):
+        url1 = "http://example.com:80/foo.php"
+        url2 = "https://example.com:443/foo.php"
+        exp1 = "http://example.com/foo.php"
+        exp2 = "https://example.com/foo.php"
+        method = "GET"
+
+        req = oauth.Request(method, url1)
+        self.assertEquals(req.normalized_url, exp1)
+        self.assertEquals(req.url, url1)
+
+        req = oauth.Request(method, url2)
+        self.assertEquals(req.normalized_url, exp2)
+        self.assertEquals(req.url, url2)
+
+    def test_bad_url(self):
+        request = oauth.Request()
+        try:
+            request.url = "ftp://example.com"
+            self.fail("Invalid URL scheme was accepted.")
+        except ValueError:
+            pass
+
+    def test_unset_consumer_and_token(self):
+        consumer = oauth.Consumer('my_consumer_key', 'my_consumer_secret')
+        token = oauth.Token('my_key', 'my_secret')
+        request = oauth.Request("GET", "http://example.com/fetch.php")
+        request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer,
+            token)
+
+        self.assertEquals(consumer.key, request['oauth_consumer_key'])
+        self.assertEquals(token.key, request['oauth_token'])
+
+    def test_no_url_set(self):
+        consumer = oauth.Consumer('my_consumer_key', 'my_consumer_secret')
+        token = oauth.Token('my_key', 'my_secret')
+        request = oauth.Request()
+
+        try:
+            try:
+                request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), 
+                    consumer, token)
+            except TypeError:
+                self.fail("Signature method didn't check for a normalized URL.")
+        except ValueError:
+            pass
+
+    def test_url_query(self):
+        url = "https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10"
+        normalized_url = urlparse.urlunparse(urlparse.urlparse(url)[:3] + (None, None, None))
+        method = "GET"
+        
+        req = oauth.Request(method, url)
+        self.assertEquals(req.url, url)
+        self.assertEquals(req.normalized_url, normalized_url)
+
+    def test_get_parameter(self):
+        url = "http://example.com"
+        method = "GET"
+        params = {'oauth_consumer' : 'asdf'}
+        req = oauth.Request(method, url, parameters=params)
+
+        self.assertEquals(req.get_parameter('oauth_consumer'), 'asdf')
+        self.assertRaises(oauth.Error, req.get_parameter, 'blah')
+
+    def test_get_nonoauth_parameters(self):
+
+        oauth_params = {
+            'oauth_consumer': 'asdfasdfasdf'
+        }
+
+        other_params = {
+            'foo': 'baz',
+            'bar': 'foo',
+            'multi': ['FOO','BAR']
+        }
+
+        params = oauth_params
+        params.update(other_params)
+
+        req = oauth.Request("GET", "http://example.com", params)
+        self.assertEquals(other_params, req.get_nonoauth_parameters())
+
+    def test_to_header(self):
+        realm = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+        }
+
+        req = oauth.Request("GET", realm, params)
+        header, value = req.to_header(realm).items()[0]
+
+        parts = value.split('OAuth ')
+        vars = parts[1].split(', ')
+        self.assertTrue(len(vars), (len(params) + 1))
+
+        res = {}
+        for v in vars:
+            var, val = v.split('=')
+            res[var] = urllib.unquote(val.strip('"'))
+
+        self.assertEquals(realm, res['realm'])
+        del res['realm']
+
+        self.assertTrue(len(res), len(params))
+
+        for key, val in res.items():
+            self.assertEquals(val, params.get(key))
+
+    def test_to_postdata(self):
+        realm = "http://sp.example.com/"
+
+        params = {
+            'multi': ['FOO','BAR'],
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+        }
+
+        req = oauth.Request("GET", realm, params)
+
+        flat = [('multi','FOO'),('multi','BAR')]
+        del params['multi']
+        flat.extend(params.items())
+        kf = lambda x: x[0]
+        self.assertEquals(sorted(flat, key=kf), sorted(parse_qsl(req.to_postdata()), key=kf))
+
+    def test_to_url(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+        }
+
+        req = oauth.Request("GET", url, params)
+        exp = urlparse.urlparse("%s?%s" % (url, urllib.urlencode(params)))
+        res = urlparse.urlparse(req.to_url())
+        self.assertEquals(exp.scheme, res.scheme)
+        self.assertEquals(exp.netloc, res.netloc)
+        self.assertEquals(exp.path, res.path)
+
+        a = parse_qs(exp.query)
+        b = parse_qs(res.query)
+        self.assertEquals(a, b)
+    
+    def test_to_url_with_query(self):
+        url = "https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+        }
+
+        req = oauth.Request("GET", url, params)
+        # Note: the url above already has query parameters, so append new ones with &
+        exp = urlparse.urlparse("%s&%s" % (url, urllib.urlencode(params)))
+        res = urlparse.urlparse(req.to_url())
+        self.assertEquals(exp.scheme, res.scheme)
+        self.assertEquals(exp.netloc, res.netloc)
+        self.assertEquals(exp.path, res.path)
+
+        a = parse_qs(exp.query)
+        b = parse_qs(res.query)
+        self.assertTrue('alt' in b)
+        self.assertTrue('max-contacts' in b)
+        self.assertEquals(b['alt'], ['json'])
+        self.assertEquals(b['max-contacts'], ['10'])
+        self.assertEquals(a, b)
+
+    def test_signature_base_string_with_query(self):
+        url = "https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10"
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+        }
+        req = oauth.Request("GET", url, params)
+        self.assertEquals(req.normalized_url, 'https://www.google.com/m8/feeds/contacts/default/full/')
+        self.assertEquals(req.url, 'https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10')
+        normalized_params = parse_qsl(req.get_normalized_parameters())
+        self.assertTrue(len(normalized_params), len(params) + 2)
+        normalized_params = dict(normalized_params)
+        for key, value in params.iteritems():
+            if key == 'oauth_signature':
+                continue
+            self.assertEquals(value, normalized_params[key])
+        self.assertEquals(normalized_params['alt'], 'json')
+        self.assertEquals(normalized_params['max-contacts'], '10')
+
+    def test_get_normalized_parameters(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'multi': ['FOO','BAR'],
+        }
+
+        req = oauth.Request("GET", url, params)
+
+        res = req.get_normalized_parameters()
+        
+        srtd = [(k, v if type(v) != ListType else sorted(v)) for k,v in sorted(params.items())]
+
+        self.assertEquals(urllib.urlencode(srtd, True), res)
+
+    def test_get_normalized_parameters_ignores_auth_signature(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_signature': "some-random-signature-%d" % random.randint(1000, 2000),
+            'oauth_token': "ad180jjd733klru7",
+        }
+
+        req = oauth.Request("GET", url, params)
+
+        res = req.get_normalized_parameters()
+
+        self.assertNotEquals(urllib.urlencode(sorted(params.items())), res)
+
+        foo = params.copy()
+        del foo["oauth_signature"]
+        self.assertEqual(urllib.urlencode(sorted(foo.items())), res)
+
+    def test_set_signature_method(self):
+        consumer = oauth.Consumer('key', 'secret')
+        client = oauth.Client(consumer)
+
+        class Blah:
+            pass
+
+        try:
+            client.set_signature_method(Blah())
+            self.fail("Client.set_signature_method() accepted invalid method.")
+        except ValueError:
+            pass
+
+        m = oauth.SignatureMethod_HMAC_SHA1()
+        client.set_signature_method(m)
+        self.assertEquals(m, client.method)
+
+    def test_get_normalized_string_escapes_spaces_properly(self):
+        url = "http://sp.example.com/"
+        params = {
+            "some_random_data": random.randint(100, 1000),
+            "data": "This data with a random number (%d) has spaces!" % random.randint(1000, 2000),
+        }
+
+        req = oauth.Request("GET", url, params)
+        res = req.get_normalized_parameters()
+        expected = urllib.urlencode(sorted(params.items())).replace('+', '%20')
+        self.assertEqual(expected, res)
+
+    def test_sign_request(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200"
+        }
+
+        tok = oauth.Token(key="tok-test-key", secret="tok-test-secret")
+        con = oauth.Consumer(key="con-test-key", secret="con-test-secret")
+
+        params['oauth_token'] = tok.key
+        params['oauth_consumer_key'] = con.key
+        req = oauth.Request(method="GET", url=url, parameters=params)
+
+        methods = {
+            'TQ6vGQ5A6IZn8dmeGB4+/Jl3EMI=': oauth.SignatureMethod_HMAC_SHA1(),
+            'con-test-secret&tok-test-secret': oauth.SignatureMethod_PLAINTEXT()
+        }
+
+        for exp, method in methods.items():
+            req.sign_request(method, con, tok)
+            self.assertEquals(req['oauth_signature_method'], method.name)
+            self.assertEquals(req['oauth_signature'], exp)
+
+    def test_from_request(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+        }
+
+        req = oauth.Request("GET", url, params)
+        headers = req.to_header()
+
+        # Test from the headers
+        req = oauth.Request.from_request("GET", url, headers)
+        self.assertEquals(req.method, "GET")
+        self.assertEquals(req.url, url)
+
+        self.assertEquals(params, req.copy())
+
+        # Test with bad OAuth headers
+        bad_headers = {
+            'Authorization' : 'OAuth this is a bad header'
+        }
+
+        self.assertRaises(oauth.Error, oauth.Request.from_request, "GET",
+            url, bad_headers)
+
+        # Test getting from query string
+        qs = urllib.urlencode(params)
+        req = oauth.Request.from_request("GET", url, query_string=qs)
+
+        exp = parse_qs(qs, keep_blank_values=False)
+        for k, v in exp.iteritems():
+            exp[k] = urllib.unquote(v[0])
+
+        self.assertEquals(exp, req.copy())
+
+        # Test that a boned from_request() call returns None
+        req = oauth.Request.from_request("GET", url)
+        self.assertEquals(None, req)
+
+    def test_from_token_and_callback(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': "137131200",
+            'oauth_consumer_key': "0685bd9184jfhq22",
+            'oauth_signature_method': "HMAC-SHA1",
+            'oauth_token': "ad180jjd733klru7",
+            'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+        }
+
+        tok = oauth.Token(key="tok-test-key", secret="tok-test-secret")
+        req = oauth.Request.from_token_and_callback(tok)
+        self.assertFalse('oauth_callback' in req)
+        self.assertEquals(req['oauth_token'], tok.key)
+
+        req = oauth.Request.from_token_and_callback(tok, callback=url)
+        self.assertTrue('oauth_callback' in req)
+        self.assertEquals(req['oauth_callback'], url)
+
+    def test_from_consumer_and_token(self):
+        url = "http://sp.example.com/"
+
+        tok = oauth.Token(key="tok-test-key", secret="tok-test-secret")
+        tok.set_verifier('this_is_a_test_verifier')
+        con = oauth.Consumer(key="con-test-key", secret="con-test-secret")
+        req = oauth.Request.from_consumer_and_token(con, token=tok,
+            http_method="GET", http_url=url)
+
+        self.assertEquals(req['oauth_token'], tok.key)
+        self.assertEquals(req['oauth_consumer_key'], con.key)
+        self.assertEquals(tok.verifier, req['oauth_verifier'])
+
+class SignatureMethod_Bad(oauth.SignatureMethod):
+    name = "BAD"
+
+    def signing_base(self, request, consumer, token):
+        return ""
+
+    def sign(self, request, consumer, token):
+        return "invalid-signature"
+
+
+class TestServer(unittest.TestCase):
+    def setUp(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': "1.0",
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': int(time.time()),
+            'bar': 'blerg',
+            'multi': ['FOO','BAR'],
+            'foo': 59
+        }
+
+        self.consumer = oauth.Consumer(key="consumer-key",
+            secret="consumer-secret")
+        self.token = oauth.Token(key="token-key", secret="token-secret")
+
+        params['oauth_token'] = self.token.key
+        params['oauth_consumer_key'] = self.consumer.key
+        self.request = oauth.Request(method="GET", url=url, parameters=params)
+
+        signature_method = oauth.SignatureMethod_HMAC_SHA1()
+        self.request.sign_request(signature_method, self.consumer, self.token)
+
+    def test_init(self):
+        server = oauth.Server(signature_methods={'HMAC-SHA1' : oauth.SignatureMethod_HMAC_SHA1()})
+        self.assertTrue('HMAC-SHA1' in server.signature_methods)
+        self.assertTrue(isinstance(server.signature_methods['HMAC-SHA1'],
+            oauth.SignatureMethod_HMAC_SHA1))
+
+        server = oauth.Server()
+        self.assertEquals(server.signature_methods, {})
+
+    def test_add_signature_method(self):
+        server = oauth.Server()
+        res = server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+        self.assertTrue(len(res) == 1)
+        self.assertTrue('HMAC-SHA1' in res)
+        self.assertTrue(isinstance(res['HMAC-SHA1'],
+            oauth.SignatureMethod_HMAC_SHA1))
+
+        res = server.add_signature_method(oauth.SignatureMethod_PLAINTEXT())
+        self.assertTrue(len(res) == 2)
+        self.assertTrue('PLAINTEXT' in res)
+        self.assertTrue(isinstance(res['PLAINTEXT'],
+            oauth.SignatureMethod_PLAINTEXT))
+
+    def test_verify_request(self):
+        server = oauth.Server()
+        server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+
+        parameters = server.verify_request(self.request, self.consumer,
+            self.token)
+
+        self.assertTrue('bar' in parameters)
+        self.assertTrue('foo' in parameters)
+        self.assertTrue('multi' in parameters)
+        self.assertEquals(parameters['bar'], 'blerg')
+        self.assertEquals(parameters['foo'], 59)
+        self.assertEquals(parameters['multi'], ['FOO','BAR'])
+
+    def test_build_authenticate_header(self):
+        server = oauth.Server()
+        headers = server.build_authenticate_header('example.com')
+        self.assertTrue('WWW-Authenticate' in headers)
+        self.assertEquals('OAuth realm="example.com"', 
+            headers['WWW-Authenticate'])
+
+    def test_no_version(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': int(time.time()),
+            'bar': 'blerg',
+            'multi': ['FOO','BAR'],
+            'foo': 59
+        }
+
+        self.consumer = oauth.Consumer(key="consumer-key",
+            secret="consumer-secret")
+        self.token = oauth.Token(key="token-key", secret="token-secret")
+
+        params['oauth_token'] = self.token.key
+        params['oauth_consumer_key'] = self.consumer.key
+        self.request = oauth.Request(method="GET", url=url, parameters=params)
+
+        signature_method = oauth.SignatureMethod_HMAC_SHA1()
+        self.request.sign_request(signature_method, self.consumer, self.token)
+
+        server = oauth.Server()
+        server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+
+        parameters = server.verify_request(self.request, self.consumer,
+            self.token)
+
+    def test_invalid_version(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': '222.9922',
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': int(time.time()),
+            'bar': 'blerg',
+            'multi': ['foo','bar'],
+            'foo': 59
+        }
+
+        consumer = oauth.Consumer(key="consumer-key",
+            secret="consumer-secret")
+        token = oauth.Token(key="token-key", secret="token-secret")
+
+        params['oauth_token'] = token.key
+        params['oauth_consumer_key'] = consumer.key
+        request = oauth.Request(method="GET", url=url, parameters=params)
+
+        signature_method = oauth.SignatureMethod_HMAC_SHA1()
+        request.sign_request(signature_method, consumer, token)
+
+        server = oauth.Server()
+        server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+
+        self.assertRaises(oauth.Error, server.verify_request, request,
+            consumer, token)
+
+    def test_invalid_signature_method(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': '1.0',
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': int(time.time()),
+            'bar': 'blerg',
+            'multi': ['FOO','BAR'],
+            'foo': 59
+        }
+
+        consumer = oauth.Consumer(key="consumer-key",
+            secret="consumer-secret")
+        token = oauth.Token(key="token-key", secret="token-secret")
+
+        params['oauth_token'] = token.key
+        params['oauth_consumer_key'] = consumer.key
+        request = oauth.Request(method="GET", url=url, parameters=params)
+
+        signature_method = SignatureMethod_Bad()
+        request.sign_request(signature_method, consumer, token)
+
+        server = oauth.Server()
+        server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+
+        self.assertRaises(oauth.Error, server.verify_request, request,
+            consumer, token)
+
+    def test_missing_signature(self):
+        url = "http://sp.example.com/"
+
+        params = {
+            'oauth_version': '1.0',
+            'oauth_nonce': "4572616e48616d6d65724c61686176",
+            'oauth_timestamp': int(time.time()),
+            'bar': 'blerg',
+            'multi': ['FOO','BAR'],
+            'foo': 59
+        }
+
+        consumer = oauth.Consumer(key="consumer-key",
+            secret="consumer-secret")
+        token = oauth.Token(key="token-key", secret="token-secret")
+
+        params['oauth_token'] = token.key
+        params['oauth_consumer_key'] = consumer.key
+        request = oauth.Request(method="GET", url=url, parameters=params)
+
+        signature_method = oauth.SignatureMethod_HMAC_SHA1()
+        request.sign_request(signature_method, consumer, token)
+        del request['oauth_signature']
+
+        server = oauth.Server()
+        server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+
+        self.assertRaises(oauth.MissingSignature, server.verify_request,
+            request, consumer, token)
+
+
+# Request Token: http://oauth-sandbox.sevengoslings.net/request_token
+# Auth: http://oauth-sandbox.sevengoslings.net/authorize
+# Access Token: http://oauth-sandbox.sevengoslings.net/access_token
+# Two-legged: http://oauth-sandbox.sevengoslings.net/two_legged
+# Three-legged: http://oauth-sandbox.sevengoslings.net/three_legged
+# Key: bd37aed57e15df53
+# Secret: 0e9e6413a9ef49510a4f68ed02cd
+class TestClient(unittest.TestCase):
+#    oauth_uris = {
+#        'request_token': '/request_token.php',
+#        'access_token': '/access_token.php'
+#    }
+
+    oauth_uris = {
+        'request_token': '/request_token',
+        'authorize': '/authorize',
+        'access_token': '/access_token',
+        'two_legged': '/two_legged',
+        'three_legged': '/three_legged'
+    }
+
+    consumer_key = 'bd37aed57e15df53'
+    consumer_secret = '0e9e6413a9ef49510a4f68ed02cd'
+    host = 'http://oauth-sandbox.sevengoslings.net'
+
+    def setUp(self):
+        self.mox = mox.Mox()
+        self.consumer = oauth.Consumer(key=self.consumer_key,
+            secret=self.consumer_secret)
+
+        self.body = {
+            'foo': 'bar',
+            'bar': 'foo',
+            'multi': ['FOO','BAR'],
+            'blah': 599999
+        }
+
+    def tearDown(self):
+        self.mox.UnsetStubs()
+
+    def _uri(self, type):
+        uri = self.oauth_uris.get(type)
+        if uri is None:
+            raise KeyError("%s is not a valid OAuth URI type." % type)
+
+        return "%s%s" % (self.host, uri)
+
+    def create_simple_multipart_data(self, data):
+        boundary = '---Boundary-%d' % random.randint(1,1000)
+        crlf = '\r\n'
+        items = []
+        for key, value in data.iteritems():
+            items += [
+                '--'+boundary,
+                'Content-Disposition: form-data; name="%s"'%str(key),
+                '',
+                str(value),
+            ]
+        items += ['', '--'+boundary+'--', '']
+        content_type = 'multipart/form-data; boundary=%s' % boundary
+        return content_type, crlf.join(items)
+
+    def test_init(self):
+        class Blah():
+            pass
+
+        try:
+            client = oauth.Client(Blah())
+            self.fail("Client.__init__() accepted invalid Consumer.")
+        except ValueError:
+            pass
+
+        consumer = oauth.Consumer('token', 'secret')
+        try:
+            client = oauth.Client(consumer, Blah())
+            self.fail("Client.__init__() accepted invalid Token.")
+        except ValueError:
+            pass
+
+    def test_access_token_get(self):
+        """Test getting an access token via GET."""
+        client = oauth.Client(self.consumer, None)
+        resp, content = client.request(self._uri('request_token'), "GET")
+
+        self.assertEquals(int(resp['status']), 200)
+
+    def test_access_token_post(self):
+        """Test getting an access token via POST."""
+        client = oauth.Client(self.consumer, None)
+        resp, content = client.request(self._uri('request_token'), "POST")
+
+        self.assertEquals(int(resp['status']), 200)
+
+        res = dict(parse_qsl(content))
+        self.assertTrue('oauth_token' in res)
+        self.assertTrue('oauth_token_secret' in res)
+
+    def _two_legged(self, method):
+        client = oauth.Client(self.consumer, None)
+
+        return client.request(self._uri('two_legged'), method,
+            body=urllib.urlencode(self.body))
+
+    def test_two_legged_post(self):
+        """A test of a two-legged OAuth POST request."""
+        resp, content = self._two_legged("POST")
+
+        self.assertEquals(int(resp['status']), 200)
+
+    def test_two_legged_get(self):
+        """A test of a two-legged OAuth GET request."""
+        resp, content = self._two_legged("GET")
+        self.assertEquals(int(resp['status']), 200)
+
+    def test_multipart_post_does_not_alter_body(self):
+        self.mox.StubOutWithMock(httplib2.Http, 'request')
+        random_result = random.randint(1,100)
+
+        data = {
+            'rand-%d'%random.randint(1,100):random.randint(1,100),
+        }
+        content_type, body = self.create_simple_multipart_data(data)
+
+        client = oauth.Client(self.consumer, None)
+        uri = self._uri('two_legged')
+
+        expected_kwargs = {
+            'method':'POST',
+            'body':body,
+            'redirections':httplib2.DEFAULT_MAX_REDIRECTS,
+            'connection_type':None,
+            'headers':mox.IsA(dict),
+        }
+        httplib2.Http.request(client, uri, **expected_kwargs).AndReturn(random_result)
+
+        self.mox.ReplayAll()
+        result = client.request(uri, 'POST', headers={'Content-Type':content_type}, body=body)
+        self.assertEqual(result, random_result)
+        self.mox.VerifyAll()
+
+    def test_url_with_query_string(self):
+        self.mox.StubOutWithMock(httplib2.Http, 'request')
+        uri = 'http://example.com/foo/bar/?show=thundercats&character=snarf'
+        client = oauth.Client(self.consumer, None)
+        expected_kwargs = {
+            'method': 'GET',
+            'body': None,
+            'redirections': httplib2.DEFAULT_MAX_REDIRECTS,
+            'connection_type': None,
+            'headers': mox.IsA(dict),
+        }
+        def oauth_verifier(url):
+            req = oauth.Request.from_consumer_and_token(self.consumer, None,
+                    http_method='GET', http_url=uri, parameters={})
+            req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), self.consumer, None)
+            expected = parse_qsl(urlparse.urlparse(req.to_url()).query)
+            actual = parse_qsl(urlparse.urlparse(url).query)
+            if len(expected) != len(actual):
+                return False
+            actual = dict(actual)
+            for key, value in expected:
+                if key not in ('oauth_signature', 'oauth_nonce', 'oauth_timestamp'):
+                    if actual[key] != value:
+                        return False
+            return True
+        httplib2.Http.request(client, mox.Func(oauth_verifier), **expected_kwargs)
+        self.mox.ReplayAll()
+        client.request(uri, 'GET')
+        self.mox.VerifyAll()
+
+if __name__ == "__main__":
+    unittest.main()
+