conduit r1926 - in trunk: . conduit/modules/GoogleModule/atom conduit/modules/GoogleModule/gdata conduit/modules/GoogleModule/gdata/Crypto conduit/modules/GoogleModule/gdata/Crypto/Cipher conduit/modules/GoogleModule/gdata/Crypto/Hash conduit/modules/GoogleModule/gdata/Crypto/Protocol conduit/modules/GoogleModule/gdata/Crypto/PublicKey conduit/modules/GoogleModule/gdata/Crypto/Util conduit/modules/GoogleModule/gdata/alt conduit/modules/GoogleModule/gdata/apps conduit/modules/GoogleModule/gdata/apps/emailsettings conduit/modules/GoogleModule/gdata/apps/migration conduit/modules/GoogleModule/gdata/base conduit/modules/GoogleModule/gdata/blogger conduit/modules/GoogleModule/gdata/calendar conduit/modules/GoogleModule/gdata/codesearch conduit/modules/GoogleModule/gdata/contacts conduit/modules/GoogleModule/gdata/docs conduit/modules/GoogleModule/gdata/geo conduit/modules/GoogleModule/gdata/media conduit/modules/GoogleModule/gdata/oauth conduit/modules/GoogleModule/gdata/ photos conduit/modules/GoogleModule/gdata/spreadsheet conduit/modules/GoogleModule/gdata/tlslite conduit/modules/GoogleModule/gdata/tlslite/integration conduit/modules/GoogleModule/gdata/tlslite/utils conduit/modules/GoogleModule/gdata/webmastertools conduit/modules/GoogleModule/gdata/youtube



Author: jstowers
Date: Tue Mar 17 09:00:35 2009
New Revision: 1926
URL: http://svn.gnome.org/viewvc/conduit?rev=1926&view=rev

Log:
2009-03-17  John Stowers  <john stowers gmail com>

	* conduit/modules/GoogleModule/atom/*:
	* conduit/modules/GoogleModule/gdata/*:
	Update to python-gdata 1.2.4



Added:
   trunk/conduit/modules/GoogleModule/gdata/Crypto/
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/AES.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/ARC2.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/ARC4.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/Blowfish.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/CAST.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/DES.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/DES3.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/IDEA.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/RC5.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/XOR.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/HMAC.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/MD2.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/MD4.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/MD5.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/RIPEMD.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/SHA.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/SHA256.pyd   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/AllOrNothing.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/Chaffing.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/
   trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/DSA.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/ElGamal.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/RSA.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/pubkey.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/qNEW.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/RFC1751.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/number.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/randpool.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/test.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/Crypto/test.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/alt/
   trunk/conduit/modules/GoogleModule/gdata/alt/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/alt/appengine.py
   trunk/conduit/modules/GoogleModule/gdata/apps/emailsettings/
   trunk/conduit/modules/GoogleModule/gdata/apps/emailsettings/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/apps/emailsettings/service.py
   trunk/conduit/modules/GoogleModule/gdata/apps/migration/
   trunk/conduit/modules/GoogleModule/gdata/apps/migration/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/apps/migration/service.py
   trunk/conduit/modules/GoogleModule/gdata/client.py
   trunk/conduit/modules/GoogleModule/gdata/oauth/
   trunk/conduit/modules/GoogleModule/gdata/oauth/CHANGES.txt   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/oauth/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/oauth/rsa.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/
   trunk/conduit/modules/GoogleModule/gdata/tlslite/BaseDB.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/Checker.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/FileObject.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/HandshakeSettings.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/Session.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/SessionCache.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/SharedKeyDB.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/TLSConnection.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/TLSRecordLayer.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/VerifierDB.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/X509.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/X509CertChain.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/api.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/constants.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/errors.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/AsyncStateMachine.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/ClientHelper.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/HTTPTLSConnection.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/IMAP4_TLS.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/IntegrationHelper.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/POP3_TLS.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/SMTP_TLS.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSSocketServerMixIn.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/XMLRPCTransport.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/mathtls.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/messages.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/AES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/ASN1Parser.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_AES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_RC4.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_TripleDES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_AES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_RC4.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_RSAKey.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_TripleDES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_AES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_RC4.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_RSAKey.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_TripleDES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_AES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_RC4.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_RSAKey.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/RC4.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/RSAKey.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/TripleDES.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/__init__.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/cipherfactory.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/codec.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/compat.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/cryptomath.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/dateFuncs.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/entropy.c   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/hmac.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/jython_compat.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/keyfactory.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/rijndael.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/win32prng.c   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/xmltools.py   (contents, props changed)
   trunk/conduit/modules/GoogleModule/gdata/webmastertools/
   trunk/conduit/modules/GoogleModule/gdata/webmastertools/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/webmastertools/service.py
Modified:
   trunk/ChangeLog
   trunk/conduit/modules/GoogleModule/atom/__init__.py
   trunk/conduit/modules/GoogleModule/atom/service.py
   trunk/conduit/modules/GoogleModule/gdata/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/apps/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/apps/service.py
   trunk/conduit/modules/GoogleModule/gdata/auth.py
   trunk/conduit/modules/GoogleModule/gdata/base/service.py
   trunk/conduit/modules/GoogleModule/gdata/blogger/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/blogger/service.py
   trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/calendar/service.py
   trunk/conduit/modules/GoogleModule/gdata/codesearch/service.py
   trunk/conduit/modules/GoogleModule/gdata/contacts/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/contacts/service.py
   trunk/conduit/modules/GoogleModule/gdata/docs/service.py
   trunk/conduit/modules/GoogleModule/gdata/geo/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/media/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/photos/service.py
   trunk/conduit/modules/GoogleModule/gdata/service.py
   trunk/conduit/modules/GoogleModule/gdata/spreadsheet/service.py
   trunk/conduit/modules/GoogleModule/gdata/spreadsheet/text_db.py
   trunk/conduit/modules/GoogleModule/gdata/test_data.py
   trunk/conduit/modules/GoogleModule/gdata/urlfetch.py
   trunk/conduit/modules/GoogleModule/gdata/youtube/__init__.py
   trunk/conduit/modules/GoogleModule/gdata/youtube/service.py

Modified: trunk/conduit/modules/GoogleModule/atom/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/atom/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/atom/__init__.py	Tue Mar 17 09:00:35 2009
@@ -65,9 +65,11 @@
 # This encoding is used for converting strings before translating the XML
 # into an object.
 XML_STRING_ENCODING = 'utf-8'
-# The desired string encoding for object members.
+# The desired string encoding for object members. set or monkey-patch to 
+# unicode if you want object members to be Python unicode strings, instead of
+# encoded strings
 MEMBER_STRING_ENCODING = 'utf-8'
-
+#MEMBER_STRING_ENCODING = unicode
 
 def CreateClassFromXMLString(target_class, xml_string, string_encoding=None):
   """Creates an instance of the target class from the string contents.
@@ -147,7 +149,10 @@
       self._ConvertElementAttributeToMember(attribute, value)
     # Encode the text string according to the desired encoding type. (UTF-8)
     if tree.text:
-      self.text = tree.text.encode(MEMBER_STRING_ENCODING)
+      if MEMBER_STRING_ENCODING is unicode:
+        self.text = tree.text
+      else:
+        self.text = tree.text.encode(MEMBER_STRING_ENCODING)
     
   def _ConvertElementTreeToMember(self, child_tree, current_class=None):
     self.extension_elements.append(_ExtensionElementFromElementTree(
@@ -156,7 +161,10 @@
   def _ConvertElementAttributeToMember(self, attribute, value):
     # Encode the attribute value's string with the desired type Default UTF-8
     if value:
-      self.extension_attributes[attribute] = value.encode(
+      if MEMBER_STRING_ENCODING is unicode:
+        self.extension_attributes[attribute] = value
+      else:
+        self.extension_attributes[attribute] = value.encode(
           MEMBER_STRING_ENCODING)
 
   # One method to create an ElementTree from an object
@@ -165,12 +173,16 @@
       child._BecomeChildElement(tree)
     for attribute, value in self.extension_attributes.iteritems():
       if value:
-        # Decode the value from the desired encoding (default UTF-8).
-        tree.attrib[attribute] = value.decode(MEMBER_STRING_ENCODING)
-    if self.text and not isinstance(self.text, unicode):
-      tree.text = self.text.decode(MEMBER_STRING_ENCODING)
-    else:
-      tree.text = self.text 
+        if isinstance(value, unicode) or MEMBER_STRING_ENCODING is unicode:
+          tree.attrib[attribute] = value
+        else:
+          # Decode the value from the desired encoding (default UTF-8).
+          tree.attrib[attribute] = value.decode(MEMBER_STRING_ENCODING)
+    if self.text:
+      if isinstance(self.text, unicode) or MEMBER_STRING_ENCODING is unicode:
+        tree.text = self.text 
+      else:
+        tree.text = self.text.decode(MEMBER_STRING_ENCODING)
 
   def FindExtensions(self, tag=None, namespace=None):
     """Searches extension elements for child nodes with the desired name.
@@ -249,7 +261,10 @@
       # desired value (using self.__dict__).
       if value:
         # Encode the string to capture non-ascii characters (default UTF-8)
-        setattr(self, self.__class__._attributes[attribute], 
+        if MEMBER_STRING_ENCODING is unicode:
+          setattr(self, self.__class__._attributes[attribute], value)
+        else:
+          setattr(self, self.__class__._attributes[attribute], 
                 value.encode(MEMBER_STRING_ENCODING))
     else:
       ExtensionContainer._ConvertElementAttributeToMember(self, attribute, 
@@ -275,7 +290,10 @@
     for xml_attribute, member_name in self.__class__._attributes.iteritems():
       member = getattr(self, member_name)
       if member is not None:
-        tree.attrib[xml_attribute] = member
+        if isinstance(member, unicode) or MEMBER_STRING_ENCODING is unicode:
+          tree.attrib[xml_attribute] = member
+        else:
+          tree.attrib[xml_attribute] = member.decode(MEMBER_STRING_ENCODING)
     # Lastly, call the ExtensionContainers's _AddMembersToElementTree to 
     # convert any extension attributes.
     ExtensionContainer._AddMembersToElementTree(self, tree)

Modified: trunk/conduit/modules/GoogleModule/atom/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/atom/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/atom/service.py	Tue Mar 17 09:00:35 2009
@@ -27,7 +27,13 @@
        used to specify information about the request.
 """
 
-__author__ = 'api.jscudder (Jeffrey Scudder)'
+__author__ = 'api.jscudder (Jeff Scudder)'
+
+
+import atom.http_interface
+import atom.url
+import atom.http
+import atom.token_store
 
 import os
 import httplib
@@ -35,6 +41,7 @@
 import re
 import base64
 import socket
+import warnings
 try:
   from xml.etree import cElementTree as ElementTree
 except ImportError:
@@ -47,9 +54,6 @@
       from elementtree import ElementTree
 
 
-URL_REGEX = re.compile('http(s)?\://([\w\.-]*)(\:(\d+))?(/.*)?')
-
-
 class AtomService(object):
   """Performs Atom Publishing Protocol CRUD operations.
   
@@ -59,10 +63,22 @@
   # Default values for members
   port = 80
   ssl = False
-  # If debug is True, the HTTPConnection will display debug information
-  debug = False
+  # Set the current_token to force the AtomService to use this token
+  # instead of searching for an appropriate token in the token_store.
+  current_token = None
+  auto_store_tokens = True
+  auto_set_current_token = True
+
+  def _get_override_token(self):
+    return self.current_token
+
+  def _set_override_token(self, token):
+    self.current_token = token
 
-  def __init__(self, server=None, additional_headers=None):
+  override_token = property(_get_override_token, _set_override_token)
+
+  def __init__(self, server=None, additional_headers=None, 
+      application_name='', http_client=None, token_store=None):
     """Creates a new AtomService client.
     
     Args:
@@ -71,20 +87,50 @@
               'www.google.com'
       additional_headers: dict (optional) Any additional HTTP headers which
                           should be included with CRUD operations.
+      http_client: An object responsible for making HTTP requests using a
+                   request method. If none is provided, a new instance of
+                   atom.http.ProxiedHttpClient will be used.
+      token_store: Keeps a collection of authorization tokens which can be
+                   applied to requests for a specific URLs. Critical methods are
+                   find_token based on a URL (atom.url.Url or a string), add_token,
+                   and remove_token.
     """
-
+    self.http_client = http_client or atom.http.ProxiedHttpClient()
+    self.token_store = token_store or atom.token_store.TokenStore()
     self.server = server
     self.additional_headers = additional_headers or {}
-
-    self.additional_headers['User-Agent'] = 'Python Google Data Client Lib'
-
-  def _ProcessUrl(self, url, for_proxy=False):
-    """Processes a passed URL.  If the URL does not begin with https?, then
-    the default value for self.server is used"""
-    return ProcessUrl(self, url, for_proxy=for_proxy)
+    self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % (
+        application_name,)
+    # If debug is True, the HTTPConnection will display debug information
+    self._set_debug(False)
+
+  def _get_debug(self):
+    return self.http_client.debug
+
+  def _set_debug(self, value):
+    self.http_client.debug = value
+
+  debug = property(_get_debug, _set_debug, 
+      doc='If True, HTTP debug information is printed.')
+
+  def use_basic_auth(self, username, password, scopes=None):
+    if username is not None and password is not None:
+      if scopes is None:
+        scopes = [atom.token_store.SCOPE_ALL]
+      base_64_string = base64.encodestring('%s:%s' % (username, password))
+      token = BasicAuthToken('Basic %s' % base_64_string.strip(), 
+          scopes=[atom.token_store.SCOPE_ALL])
+      if self.auto_set_current_token:
+        self.current_token = token
+      if self.auto_store_tokens:
+        return self.token_store.add_token(token)
+      return True
+    return False
 
   def UseBasicAuth(self, username, password, for_proxy=False):
     """Sets an Authenticaiton: Basic HTTP header containing plaintext.
+
+    Deprecated, use use_basic_auth instead.
     
     The username and password are base64 encoded and added to an HTTP header
     which will be included in each request. Note that your username and 
@@ -94,31 +140,41 @@
       username: str
       password: str
     """
-    UseBasicAuth(self, username, password, for_proxy=for_proxy)
+    self.use_basic_auth(username, password)
 
-  def PrepareConnection(self, full_uri):
-    """Opens a connection to the server based on the full URI.
-
-    Examines the target URI and the proxy settings, which are set as 
-    environment variables, to open a connection with the server. This 
-    connection is used to make an HTTP request.
-
-    Args:
-      full_uri: str Which is the target relative (lacks protocol and host) or
-      absolute URL to be opened. Example:
-      'https://www.google.com/accounts/ClientLogin' or
-      'base/feeds/snippets' where the server is set to www.google.com.
+  def request(self, operation, url, data=None, headers=None, 
+      url_params=None):
+    if isinstance(url, str):
+      if not url.startswith('http') and self.ssl:
+        url = atom.url.parse_url('https://%s%s' % (self.server, url))
+      elif not url.startswith('http'):
+        url = atom.url.parse_url('http://%s%s' % (self.server, url))
+      else:
+        url = atom.url.parse_url(url)
 
-    Returns:
-      A tuple containing the httplib.HTTPConnection and the full_uri for the
-      request.
-    """
-    return PrepareConnection(self, full_uri)   
+    if url_params:
+      for name, value in url_params.iteritems():
+        url.params[name] = value
+
+    all_headers = self.additional_headers.copy()
+    if headers:
+      all_headers.update(headers)
+
+    # If the list of headers does not include a Content-Length, attempt to
+    # calculate it based on the data object.
+    if data and 'Content-Length' not in all_headers:
+      content_length = CalculateDataLength(data)
+      if content_length:
+        all_headers['Content-Length'] = str(content_length)
+
+    # Find an Authorization token for this URL if one is available.
+    if self.override_token:
+      auth_token = self.override_token
+    else:
+      auth_token = self.token_store.find_token(url)
+    return auth_token.perform_request(self.http_client, operation, url, 
+        data=data, headers=all_headers)
 
-  # Alias the old name for the above method to preserve backwards 
-  # compatibility.
-  _PrepareConnection = PrepareConnection
- 
   # CRUD operations
   def Get(self, uri, extra_headers=None, url_params=None, escape_params=True):
     """Query the APP server with the given URI
@@ -153,8 +209,8 @@
     Returns:
       httplib.HTTPResponse The server's response to the GET request.
     """
-    return HttpRequest(self, 'GET', None, uri, extra_headers=extra_headers, 
-        url_params=url_params, escape_params=escape_params)
+    return self.request('GET', uri, data=None, headers=extra_headers, 
+                        url_params=url_params)
 
   def Post(self, data, uri, extra_headers=None, url_params=None, 
            escape_params=True, content_type='application/atom+xml'):
@@ -181,9 +237,12 @@
     Returns:
       httplib.HTTPResponse Server's response to the POST request.
     """
-    return HttpRequest(self, 'POST', data, uri, extra_headers=extra_headers, 
-        url_params=url_params, escape_params=escape_params, 
-        content_type=content_type)
+    if extra_headers is None:
+      extra_headers = {}
+    if content_type:
+      extra_headers['Content-Type'] = content_type
+    return self.request('POST', uri, data=data, headers=extra_headers, 
+                        url_params=url_params)
 
   def Put(self, data, uri, extra_headers=None, url_params=None, 
            escape_params=True, content_type='application/atom+xml'):
@@ -210,9 +269,12 @@
     Returns:
       httplib.HTTPResponse Server's response to the PUT request.
     """
-    return HttpRequest(self, 'PUT', data, uri, extra_headers=extra_headers, 
-        url_params=url_params, escape_params=escape_params, 
-        content_type=content_type)
+    if extra_headers is None:
+      extra_headers = {}
+    if content_type:
+      extra_headers['Content-Type'] = content_type
+    return self.request('PUT', uri, data=data, headers=extra_headers, 
+                        url_params=url_params)
 
   def Delete(self, uri, extra_headers=None, url_params=None, 
              escape_params=True):
@@ -237,145 +299,64 @@
     Returns:
       httplib.HTTPResponse Server's response to the DELETE request.
     """
-    return HttpRequest(self, 'DELETE', None, uri, extra_headers=extra_headers, 
-        url_params=url_params, escape_params=escape_params)
-
-
-def HttpRequest(service, operation, data, uri, extra_headers=None, 
-    url_params=None, escape_params=True, content_type='application/atom+xml'):
-  """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
-
-  Usage example, perform and HTTP GET on http://www.google.com/:
-    import atom.service
-    client = atom.service.AtomService()
-    http_response = client.Get('http://www.google.com/')
-  or you could set the client.server to 'www.google.com' and use the 
-  following:
-    client.server = 'www.google.com'
-    http_response = client.Get('/')
-
-  Args:
-    service: atom.AtomService object which contains some of the parameters 
-        needed to make the request. The following members are used to 
-        construct the HTTP call: server (str), additional_headers (dict), 
-        port (int), and ssl (bool).
-    operation: str The HTTP operation to be performed. This is usually one of
-        'GET', 'POST', 'PUT', or 'DELETE'
-    data: ElementTree, filestream, list of parts, or other object which can be 
-        converted to a string. 
-        Should be set to None when performing a GET or PUT.
-        If data is a file-like object which can be read, this method will read
-        a chunk of 100K bytes at a time and send them. 
-        If the data is a list of parts to be sent, each part will be evaluated
-        and sent.
-    uri: The beginning of the URL to which the request should be sent. 
-        Examples: '/', '/base/feeds/snippets', 
-        '/m8/feeds/contacts/default/base'
-    extra_headers: dict of strings. HTTP headers which should be sent
-        in the request. These headers are in addition to those stored in 
-        service.additional_headers.
-    url_params: dict of strings. Key value pairs to be added to the URL as
-        URL parameters. For example {'foo':'bar', 'test':'param'} will 
-        become ?foo=bar&test=param.
-    escape_params: bool default True. If true, the keys and values in 
-        url_params will be URL escaped when the form is constructed 
-        (Special characters converted to %XX form.)
-    content_type: str The MIME type for the data being sent. Defaults to
-        'application/atom+xml', this is only used if data is set.
-  """
-  full_uri = BuildUri(uri, url_params, escape_params)
-  (connection, full_uri) = PrepareConnection(service, full_uri)
-
-  if extra_headers is None:
-    extra_headers = {}
+    return self.request('DELETE', uri, data=None, headers=extra_headers, 
+                        url_params=url_params)
 
-  # Turn on debug mode if the debug member is set.
-  if service.debug:
-    connection.debuglevel = 1
 
-  connection.putrequest(operation, full_uri)
+class BasicAuthToken(atom.http_interface.GenericToken):
+  def __init__(self, auth_header, scopes=None):
+    """Creates a token used to add Basic Auth headers to HTTP requests.
 
-  # If the list of headers does not include a Content-Length, attempt to 
-  # calculate it based on the data object.
-  if (data and not service.additional_headers.has_key('Content-Length') and 
-      not extra_headers.has_key('Content-Length')):
-    content_length = __CalculateDataLength(data)
-    if content_length:
-      extra_headers['Content-Length'] = content_length
-
-  if content_type:
-    extra_headers['Content-Type'] = content_type 
-
-  # Send the HTTP headers.
-  if isinstance(service.additional_headers, dict):
-    for header in service.additional_headers:
-      connection.putheader(header, service.additional_headers[header])
-  if isinstance(extra_headers, dict):
-    for header in extra_headers:
-      connection.putheader(header, extra_headers[header])
-  connection.endheaders()
+    Args:
+      auth_header: str The value for the Authorization header.
+      scopes: list of str or atom.url.Url specifying the beginnings of URLs
+          for which this token can be used. For example, if scopes contains
+          'http://example.com/foo', then this token can be used for a request to
+          'http://example.com/foo/bar' but it cannot be used for a request to
+          'http://example.com/baz'
+    """
+    self.auth_header = auth_header
+    self.scopes = scopes or []
 
-  # If there is data, send it in the request.
-  if data:
-    if isinstance(data, list):
-      for data_part in data:
-        __SendDataPart(data_part, connection)
+  def perform_request(self, http_client, operation, url, data=None,
+                      headers=None):
+    """Sets the Authorization header to the basic auth string."""
+    if headers is None:
+      headers = {'Authorization':self.auth_header}
     else:
-      __SendDataPart(data, connection)
-
-  # Return the HTTP Response from the server.
-  return connection.getresponse()
+      headers['Authorization'] = self.auth_header
+    return http_client.request(operation, url, data=data, headers=headers)
 
+  def __str__(self):
+    return self.auth_header
 
-def __SendDataPart(data, connection):
-  if isinstance(data, str):
-    #TODO add handling for unicode.
-    connection.send(data)
-    return
-  elif ElementTree.iselement(data):
-    connection.send(ElementTree.tostring(data))
-    return
-  # Check to see if data is a file-like object that has a read method.
-  elif hasattr(data, 'read'):
-    # Read the file and send it a chunk at a time.
-    while 1:
-      binarydata = data.read(100000)
-      if binarydata == '': break
-      connection.send(binarydata)
-    return
-  else:
-    # The data object was not a file.
-    # Try to convert to a string and send the data.
-    connection.send(str(data))
-    return
-
-
-def __CalculateDataLength(data):
-  """Attempts to determine the length of the data to send. 
-  
-  This method will respond with a length only if the data is a string or
-  and ElementTree element.
-
-  Args:
-    data: object If this is not a string or ElementTree element this funtion
-        will return None.
-  """
-  if isinstance(data, str):
-    return len(data)
-  elif isinstance(data, list):
-    return None
-  elif ElementTree.iselement(data):
-    return len(ElementTree.tostring(data))
-  elif hasattr(data, 'read'):
-    # If this is a file-like object, don't try to guess the length.
-    return None
-  else:
-    return len(str(data))
+  def valid_for_scope(self, url):
+    """Tells the caller if the token authorizes access to the desired URL.
+    """
+    if isinstance(url, (str, unicode)):
+      url = atom.url.parse_url(url)
+    for scope in self.scopes:
+      if scope == atom.token_store.SCOPE_ALL:
+        return True
+      if isinstance(scope, (str, unicode)):
+        scope = atom.url.parse_url(scope)
+      if scope == url:
+        return True
+      # Check the host and the path, but ignore the port and protocol.
+      elif scope.host == url.host and not scope.path:
+        return True
+      elif scope.host == url.host and scope.path and not url.path:
+        continue
+      elif scope.host == url.host and url.path.startswith(scope.path):
+        return True
+    return False
 
 
 def PrepareConnection(service, full_uri):
   """Opens a connection to the server based on the full URI.
 
+  This method is deprecated, instead use atom.http.HttpClient.request.
+
   Examines the target URI and the proxy settings, which are set as
   environment variables, to open a connection with the server. This
   connection is used to make an HTTP request.
@@ -393,7 +374,7 @@
     A tuple containing the httplib.HTTPConnection and the full_uri for the
     request.
   """
-   
+  deprecation('calling deprecated function PrepareConnection')
   (server, port, ssl, partial_uri) = ProcessUrl(service, full_uri)
   if ssl:
     # destination is https
@@ -474,6 +455,8 @@
 
 def UseBasicAuth(service, username, password, for_proxy=False):
   """Sets an Authenticaiton: Basic HTTP header containing plaintext.
+
+  Deprecated, use AtomService.use_basic_auth insread.
   
   The username and password are base64 encoded and added to an HTTP header
   which will be included in each request. Note that your username and 
@@ -486,6 +469,7 @@
     username: str
     password: str
   """
+  deprecation('calling deprecated function UseBasicAuth')
   base_64_string = base64.encodestring('%s:%s' % (username, password))
   base_64_string = base_64_string.strip()
   if for_proxy:
@@ -497,45 +481,43 @@
 
 def ProcessUrl(service, url, for_proxy=False):
   """Processes a passed URL.  If the URL does not begin with https?, then
-  the default value for server is used"""
+  the default value for server is used
 
-  server = None
-  port = 80
+  This method is deprecated, use atom.url.parse_url instead.
+  """
+  if not isinstance(url, atom.url.Url):
+    url = atom.url.parse_url(url)
+
+  server = url.host
   ssl = False
-  if hasattr(service, 'server'):
-    server = service.server
-  else:
-    server = service
-  if not for_proxy:
+  port = 80
+
+  if not server:
+    if hasattr(service, 'server'):
+      server = service.server
+    else:
+      server = service
+    if not url.protocol and hasattr(service, 'ssl'):
+      ssl = service.ssl
     if hasattr(service, 'port'):
       port = service.port
-    if hasattr(service, 'ssl'):
-      ssl = service.ssl
-  uri = url
-
-  m = URL_REGEX.match(url)
-
-  if m is None:
-    return (server, port, ssl, uri)
   else:
-    if m.group(1) is not None:
-      port = 443
+    if url.protocol == 'https':
       ssl = True
-    if m.group(3) is None:
-      server = m.group(2)
-    else:
-      server = m.group(2)
-      port = int(m.group(4))
-    if m.group(5) is not None:
-      uri = m.group(5)
-    else:
-      uri = '/'
-    return (server, port, ssl, uri)
+    elif url.protocol == 'http':
+      ssl = False
+    if url.port:
+      port = int(url.port)
+    elif port == 80 and ssl:
+      port = 443
 
+  return (server, port, ssl, url.get_request_uri())
 
 def DictionaryToParamList(url_parameters, escape_params=True):
   """Convert a dictionary of URL arguments into a URL parameter string.
 
+  This function is deprcated, use atom.url.Url instead.
+
   Args:
     url_parameters: The dictionaty of key-value pairs which will be converted
                     into URL parameters. For example,
@@ -555,13 +537,14 @@
                      for param, value in (url_parameters or {}).items()]
   # Turn parameter-value tuples into a list of strings in the form
   # 'PARAMETER=VALUE'.
-
   return ['='.join(x) for x in parameter_tuples]
 
 
 def BuildUri(uri, url_params=None, escape_params=True):
   """Converts a uri string and a collection of parameters into a URI.
 
+  This function is deprcated, use atom.url.Url instead.
+
   Args:
     uri: string
     url_params: dict (optional)
@@ -600,3 +583,144 @@
     full_uri = uri
         
   return full_uri
+
+  
+def HttpRequest(service, operation, data, uri, extra_headers=None, 
+    url_params=None, escape_params=True, content_type='application/atom+xml'):
+  """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
+  
+  This method is deprecated, use atom.http.HttpClient.request instead.
+
+  Usage example, perform and HTTP GET on http://www.google.com/:
+    import atom.service
+    client = atom.service.AtomService()
+    http_response = client.Get('http://www.google.com/')
+  or you could set the client.server to 'www.google.com' and use the 
+  following:
+    client.server = 'www.google.com'
+    http_response = client.Get('/')
+
+  Args:
+    service: atom.AtomService object which contains some of the parameters 
+        needed to make the request. The following members are used to 
+        construct the HTTP call: server (str), additional_headers (dict), 
+        port (int), and ssl (bool).
+    operation: str The HTTP operation to be performed. This is usually one of
+        'GET', 'POST', 'PUT', or 'DELETE'
+    data: ElementTree, filestream, list of parts, or other object which can be 
+        converted to a string. 
+        Should be set to None when performing a GET or PUT.
+        If data is a file-like object which can be read, this method will read
+        a chunk of 100K bytes at a time and send them. 
+        If the data is a list of parts to be sent, each part will be evaluated
+        and sent.
+    uri: The beginning of the URL to which the request should be sent. 
+        Examples: '/', '/base/feeds/snippets', 
+        '/m8/feeds/contacts/default/base'
+    extra_headers: dict of strings. HTTP headers which should be sent
+        in the request. These headers are in addition to those stored in 
+        service.additional_headers.
+    url_params: dict of strings. Key value pairs to be added to the URL as
+        URL parameters. For example {'foo':'bar', 'test':'param'} will 
+        become ?foo=bar&test=param.
+    escape_params: bool default True. If true, the keys and values in 
+        url_params will be URL escaped when the form is constructed 
+        (Special characters converted to %XX form.)
+    content_type: str The MIME type for the data being sent. Defaults to
+        'application/atom+xml', this is only used if data is set.
+  """
+  deprecation('call to deprecated function HttpRequest')
+  full_uri = BuildUri(uri, url_params, escape_params)
+  (connection, full_uri) = PrepareConnection(service, full_uri)
+
+  if extra_headers is None:
+    extra_headers = {}
+
+  # Turn on debug mode if the debug member is set.
+  if service.debug:
+    connection.debuglevel = 1
+
+  connection.putrequest(operation, full_uri)
+
+  # If the list of headers does not include a Content-Length, attempt to 
+  # calculate it based on the data object.
+  if (data and not service.additional_headers.has_key('Content-Length') and 
+      not extra_headers.has_key('Content-Length')):
+    content_length = CalculateDataLength(data)
+    if content_length:
+      extra_headers['Content-Length'] = str(content_length)
+
+  if content_type:
+    extra_headers['Content-Type'] = content_type 
+
+  # Send the HTTP headers.
+  if isinstance(service.additional_headers, dict):
+    for header in service.additional_headers:
+      connection.putheader(header, service.additional_headers[header])
+  if isinstance(extra_headers, dict):
+    for header in extra_headers:
+      connection.putheader(header, extra_headers[header])
+  connection.endheaders()
+
+  # If there is data, send it in the request.
+  if data:
+    if isinstance(data, list):
+      for data_part in data:
+        __SendDataPart(data_part, connection)
+    else:
+      __SendDataPart(data, connection)
+
+  # Return the HTTP Response from the server.
+  return connection.getresponse()
+  
+
+def __SendDataPart(data, connection):
+  """This method is deprecated, use atom.http._send_data_part"""
+  deprecated('call to deprecated function __SendDataPart')
+  if isinstance(data, str):
+    #TODO add handling for unicode.
+    connection.send(data)
+    return
+  elif ElementTree.iselement(data):
+    connection.send(ElementTree.tostring(data))
+    return
+  # Check to see if data is a file-like object that has a read method.
+  elif hasattr(data, 'read'):
+    # Read the file and send it a chunk at a time.
+    while 1:
+      binarydata = data.read(100000)
+      if binarydata == '': break
+      connection.send(binarydata)
+    return
+  else:
+    # The data object was not a file.
+    # Try to convert to a string and send the data.
+    connection.send(str(data))
+    return
+
+
+def CalculateDataLength(data):
+  """Attempts to determine the length of the data to send. 
+  
+  This method will respond with a length only if the data is a string or
+  and ElementTree element.
+
+  Args:
+    data: object If this is not a string or ElementTree element this funtion
+        will return None.
+  """
+  if isinstance(data, str):
+    return len(data)
+  elif isinstance(data, list):
+    return None
+  elif ElementTree.iselement(data):
+    return len(ElementTree.tostring(data))
+  elif hasattr(data, 'read'):
+    # If this is a file-like object, don't try to guess the length.
+    return None
+  else:
+    return len(str(data))
+    
+
+def deprecation(message):
+  warnings.warn(message, DeprecationWarning, stacklevel=2)

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/AES.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/ARC2.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/ARC4.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/Blowfish.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/CAST.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/DES.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/DES3.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/IDEA.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/RC5.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/XOR.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Cipher/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,33 @@
+"""Secret-key encryption algorithms.
+
+Secret-key encryption algorithms transform plaintext in some way that
+is dependent on a key, producing ciphertext. This transformation can
+easily be reversed, if (and, hopefully, only if) one knows the key.
+
+The encryption modules here all support the interface described in PEP
+272, "API for Block Encryption Algorithms".
+
+If you don't know which algorithm to choose, use AES because it's
+standard and has undergone a fair bit of examination.
+
+Crypto.Cipher.AES         Advanced Encryption Standard
+Crypto.Cipher.ARC2        Alleged RC2
+Crypto.Cipher.ARC4        Alleged RC4
+Crypto.Cipher.Blowfish
+Crypto.Cipher.CAST
+Crypto.Cipher.DES         The Data Encryption Standard.  Very commonly used
+                          in the past, but today its 56-bit keys are too small.
+Crypto.Cipher.DES3        Triple DES.
+Crypto.Cipher.IDEA
+Crypto.Cipher.RC5
+Crypto.Cipher.XOR         The simple XOR cipher.
+"""
+
+__all__ = ['AES', 'ARC2', 'ARC4',
+           'Blowfish', 'CAST', 'DES', 'DES3', 'IDEA', 'RC5',
+           'XOR'
+           ]
+
+__revision__ = "$Id: __init__.py,v 1.7 2003/02/28 15:28:35 akuchling Exp $"
+
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/HMAC.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/HMAC.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,108 @@
+"""HMAC (Keyed-Hashing for Message Authentication) Python module.
+
+Implements the HMAC algorithm as described by RFC 2104.
+
+This is just a copy of the Python 2.2 HMAC module, modified to work when
+used on versions of Python before 2.2.
+"""
+
+__revision__ = "$Id: HMAC.py,v 1.5 2002/07/25 17:19:02 z3p Exp $"
+
+import string
+
+def _strxor(s1, s2):
+    """Utility method. XOR the two strings s1 and s2 (must have same length).
+    """
+    return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), s1, s2))
+
+# The size of the digests returned by HMAC depends on the underlying
+# hashing module used.
+digest_size = None
+
+class HMAC:
+    """RFC2104 HMAC class.
+
+    This supports the API for Cryptographic Hash Functions (PEP 247).
+    """
+
+    def __init__(self, key, msg = None, digestmod = None):
+        """Create a new HMAC object.
+
+        key:       key for the keyed hash object.
+        msg:       Initial input for the hash, if provided.
+        digestmod: A module supporting PEP 247. Defaults to the md5 module.
+        """
+        if digestmod == None:
+            import md5
+            digestmod = md5
+
+        self.digestmod = digestmod
+        self.outer = digestmod.new()
+        self.inner = digestmod.new()
+        try:
+            self.digest_size = digestmod.digest_size
+        except AttributeError:
+            self.digest_size = len(self.outer.digest())
+
+        blocksize = 64
+        ipad = "\x36" * blocksize
+        opad = "\x5C" * blocksize
+
+        if len(key) > blocksize:
+            key = digestmod.new(key).digest()
+
+        key = key + chr(0) * (blocksize - len(key))
+        self.outer.update(_strxor(key, opad))
+        self.inner.update(_strxor(key, ipad))
+        if (msg):
+            self.update(msg)
+
+##    def clear(self):
+##        raise NotImplementedError, "clear() method not available in HMAC."
+
+    def update(self, msg):
+        """Update this hashing object with the string msg.
+        """
+        self.inner.update(msg)
+
+    def copy(self):
+        """Return a separate copy of this hashing object.
+
+        An update to this copy won't affect the original object.
+        """
+        other = HMAC("")
+        other.digestmod = self.digestmod
+        other.inner = self.inner.copy()
+        other.outer = self.outer.copy()
+        return other
+
+    def digest(self):
+        """Return the hash value of this hashing object.
+
+        This returns a string containing 8-bit data.  The object is
+        not altered in any way by this function; you can continue
+        updating the object after calling this function.
+        """
+        h = self.outer.copy()
+        h.update(self.inner.digest())
+        return h.digest()
+
+    def hexdigest(self):
+        """Like digest(), but returns a string of hexadecimal digits instead.
+        """
+        return "".join([string.zfill(hex(ord(x))[2:], 2)
+                        for x in tuple(self.digest())])
+
+def new(key, msg = None, digestmod = None):
+    """Create a new hashing object and return it.
+
+    key: The starting key for the hash.
+    msg: if available, will immediately be hashed into the object's starting
+    state.
+
+    You can now feed arbitrary strings into the object using its update()
+    method, and can ask for the hash value at any time by calling its digest()
+    method.
+    """
+    return HMAC(key, msg, digestmod)
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/MD2.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/MD4.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/MD5.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/MD5.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,13 @@
+
+# Just use the MD5 module from the Python standard library
+
+__revision__ = "$Id: MD5.py,v 1.4 2002/07/11 14:31:19 akuchling Exp $"
+
+from md5 import *
+
+import md5
+if hasattr(md5, 'digestsize'):
+    digest_size = digestsize
+    del digestsize
+del md5
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/RIPEMD.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/SHA.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/SHA.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,11 @@
+
+# Just use the SHA module from the Python standard library
+
+__revision__ = "$Id: SHA.py,v 1.4 2002/07/11 14:31:19 akuchling Exp $"
+
+from sha import *
+import sha
+if hasattr(sha, 'digestsize'):
+    digest_size = digestsize
+    del digestsize
+del sha

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/SHA256.pyd
==============================================================================
Binary file. No diff available.

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Hash/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,24 @@
+"""Hashing algorithms
+
+Hash functions take arbitrary strings as input, and produce an output
+of fixed size that is dependent on the input; it should never be
+possible to derive the input data given only the hash function's
+output.  Hash functions can be used simply as a checksum, or, in
+association with a public-key algorithm, can be used to implement
+digital signatures.
+
+The hashing modules here all support the interface described in PEP
+247, "API for Cryptographic Hash Functions".
+
+Submodules:
+Crypto.Hash.HMAC          RFC 2104: Keyed-Hashing for Message Authentication
+Crypto.Hash.MD2
+Crypto.Hash.MD4
+Crypto.Hash.MD5
+Crypto.Hash.RIPEMD
+Crypto.Hash.SHA
+"""
+
+__all__ = ['HMAC', 'MD2', 'MD4', 'MD5', 'RIPEMD', 'SHA', 'SHA256']
+__revision__ = "$Id: __init__.py,v 1.6 2003/12/19 14:24:25 akuchling Exp $"
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/AllOrNothing.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/AllOrNothing.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,295 @@
+"""This file implements all-or-nothing package transformations.
+
+An all-or-nothing package transformation is one in which some text is
+transformed into message blocks, such that all blocks must be obtained before
+the reverse transformation can be applied.  Thus, if any blocks are corrupted
+or lost, the original message cannot be reproduced.
+
+An all-or-nothing package transformation is not encryption, although a block
+cipher algorithm is used.  The encryption key is randomly generated and is
+extractable from the message blocks.
+
+This class implements the All-Or-Nothing package transformation algorithm
+described in:
+
+Ronald L. Rivest.  "All-Or-Nothing Encryption and The Package Transform"
+http://theory.lcs.mit.edu/~rivest/fusion.pdf
+
+"""
+
+__revision__ = "$Id: AllOrNothing.py,v 1.8 2003/02/28 15:23:20 akuchling Exp $"
+
+import operator
+import string
+from Crypto.Util.number import bytes_to_long, long_to_bytes
+
+
+
+class AllOrNothing:
+    """Class implementing the All-or-Nothing package transform.
+
+    Methods for subclassing:
+
+        _inventkey(key_size):
+            Returns a randomly generated key.  Subclasses can use this to
+            implement better random key generating algorithms.  The default
+            algorithm is probably not very cryptographically secure.
+
+    """
+
+    def __init__(self, ciphermodule, mode=None, IV=None):
+        """AllOrNothing(ciphermodule, mode=None, IV=None)
+
+        ciphermodule is a module implementing the cipher algorithm to
+        use.  It must provide the PEP272 interface.
+
+        Note that the encryption key is randomly generated
+        automatically when needed.  Optional arguments mode and IV are
+        passed directly through to the ciphermodule.new() method; they
+        are the feedback mode and initialization vector to use.  All
+        three arguments must be the same for the object used to create
+        the digest, and to undigest'ify the message blocks.
+        """
+
+        self.__ciphermodule = ciphermodule
+        self.__mode = mode
+        self.__IV = IV
+        self.__key_size = ciphermodule.key_size
+        if self.__key_size == 0:
+            self.__key_size = 16
+
+    __K0digit = chr(0x69)
+
+    def digest(self, text):
+        """digest(text:string) : [string]
+
+        Perform the All-or-Nothing package transform on the given
+        string.  Output is a list of message blocks describing the
+        transformed text, where each block is a string of bit length equal
+        to the ciphermodule's block_size.
+        """
+
+        # generate a random session key and K0, the key used to encrypt the
+        # hash blocks.  Rivest calls this a fixed, publically-known encryption
+        # key, but says nothing about the security implications of this key or
+        # how to choose it.
+        key = self._inventkey(self.__key_size)
+        K0 = self.__K0digit * self.__key_size
+
+        # we need two cipher objects here, one that is used to encrypt the
+        # message blocks and one that is used to encrypt the hashes.  The
+        # former uses the randomly generated key, while the latter uses the
+        # well-known key.
+        mcipher = self.__newcipher(key)
+        hcipher = self.__newcipher(K0)
+
+        # Pad the text so that its length is a multiple of the cipher's
+        # block_size.  Pad with trailing spaces, which will be eliminated in
+        # the undigest() step.
+        block_size = self.__ciphermodule.block_size
+        padbytes = block_size - (len(text) % block_size)
+        text = text + ' ' * padbytes
+
+        # Run through the algorithm:
+        # s: number of message blocks (size of text / block_size)
+        # input sequence: m1, m2, ... ms
+        # random key K' (`key' in the code)
+        # Compute output sequence: m'1, m'2, ... m's' for s' = s + 1
+        # Let m'i = mi ^ E(K', i) for i = 1, 2, 3, ..., s
+        # Let m's' = K' ^ h1 ^ h2 ^ ... hs
+        # where hi = E(K0, m'i ^ i) for i = 1, 2, ... s
+        #
+        # The one complication I add is that the last message block is hard
+        # coded to the number of padbytes added, so that these can be stripped
+        # during the undigest() step
+        s = len(text) / block_size
+        blocks = []
+        hashes = []
+        for i in range(1, s+1):
+            start = (i-1) * block_size
+            end = start + block_size
+            mi = text[start:end]
+            assert len(mi) == block_size
+            cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
+            mticki = bytes_to_long(mi) ^ bytes_to_long(cipherblock)
+            blocks.append(mticki)
+            # calculate the hash block for this block
+            hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size))
+            hashes.append(bytes_to_long(hi))
+
+        # Add the padbytes length as a message block
+        i = i + 1
+        cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
+        mticki = padbytes ^ bytes_to_long(cipherblock)
+        blocks.append(mticki)
+
+        # calculate this block's hash
+        hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size))
+        hashes.append(bytes_to_long(hi))
+
+        # Now calculate the last message block of the sequence 1..s'.  This
+        # will contain the random session key XOR'd with all the hash blocks,
+        # so that for undigest(), once all the hash blocks are calculated, the
+        # session key can be trivially extracted.  Calculating all the hash
+        # blocks requires that all the message blocks be received, thus the
+        # All-or-Nothing algorithm succeeds.
+        mtick_stick = bytes_to_long(key) ^ reduce(operator.xor, hashes)
+        blocks.append(mtick_stick)
+
+        # we convert the blocks to strings since in Python, byte sequences are
+        # always represented as strings.  This is more consistent with the
+        # model that encryption and hash algorithms always operate on strings.
+        return map(long_to_bytes, blocks)
+
+
+    def undigest(self, blocks):
+        """undigest(blocks : [string]) : string
+
+        Perform the reverse package transformation on a list of message
+        blocks.  Note that the ciphermodule used for both transformations
+        must be the same.  blocks is a list of strings of bit length
+        equal to the ciphermodule's block_size.
+        """
+
+        # better have at least 2 blocks, for the padbytes package and the hash
+        # block accumulator
+        if len(blocks) < 2:
+            raise ValueError, "List must be at least length 2."
+
+        # blocks is a list of strings.  We need to deal with them as long
+        # integers
+        blocks = map(bytes_to_long, blocks)
+
+        # Calculate the well-known key, to which the hash blocks are
+        # encrypted, and create the hash cipher.
+        K0 = self.__K0digit * self.__key_size
+        hcipher = self.__newcipher(K0)
+
+        # Since we have all the blocks (or this method would have been called
+        # prematurely), we can calcualte all the hash blocks.
+        hashes = []
+        for i in range(1, len(blocks)):
+            mticki = blocks[i-1] ^ i
+            hi = hcipher.encrypt(long_to_bytes(mticki))
+            hashes.append(bytes_to_long(hi))
+
+        # now we can calculate K' (key).  remember the last block contains
+        # m's' which we don't include here
+        key = blocks[-1] ^ reduce(operator.xor, hashes)
+
+        # and now we can create the cipher object
+        mcipher = self.__newcipher(long_to_bytes(key))
+        block_size = self.__ciphermodule.block_size
+
+        # And we can now decode the original message blocks
+        parts = []
+        for i in range(1, len(blocks)):
+            cipherblock = mcipher.encrypt(long_to_bytes(i, block_size))
+            mi = blocks[i-1] ^ bytes_to_long(cipherblock)
+            parts.append(mi)
+
+        # The last message block contains the number of pad bytes appended to
+        # the original text string, such that its length was an even multiple
+        # of the cipher's block_size.  This number should be small enough that
+        # the conversion from long integer to integer should never overflow
+        padbytes = int(parts[-1])
+        text = string.join(map(long_to_bytes, parts[:-1]), '')
+        return text[:-padbytes]
+
+    def _inventkey(self, key_size):
+        # TBD: Not a very secure algorithm.  Eventually, I'd like to use JHy's
+        # kernelrand module
+        import time
+        from Crypto.Util import randpool
+        # TBD: key_size * 2 to work around possible bug in RandomPool?
+        pool = randpool.RandomPool(key_size * 2)
+        while key_size > pool.entropy:
+            pool.add_event()
+
+        # we now have enough entropy in the pool to get a key_size'd key
+        return pool.get_bytes(key_size)
+
+    def __newcipher(self, key):
+        if self.__mode is None and self.__IV is None:
+            return self.__ciphermodule.new(key)
+        elif self.__IV is None:
+            return self.__ciphermodule.new(key, self.__mode)
+        else:
+            return self.__ciphermodule.new(key, self.__mode, self.__IV)
+
+
+
+if __name__ == '__main__':
+    import sys
+    import getopt
+    import base64
+
+    usagemsg = '''\
+Test module usage: %(program)s [-c cipher] [-l] [-h]
+
+Where:
+    --cipher module
+    -c module
+        Cipher module to use.  Default: %(ciphermodule)s
+
+    --aslong
+    -l
+        Print the encoded message blocks as long integers instead of base64
+        encoded strings
+
+    --help
+    -h
+        Print this help message
+'''
+
+    ciphermodule = 'AES'
+    aslong = 0
+
+    def usage(code, msg=None):
+        if msg:
+            print msg
+        print usagemsg % {'program': sys.argv[0],
+                          'ciphermodule': ciphermodule}
+        sys.exit(code)
+
+    try:
+        opts, args = getopt.getopt(sys.argv[1:],
+                                   'c:l', ['cipher=', 'aslong'])
+    except getopt.error, msg:
+        usage(1, msg)
+
+    if args:
+        usage(1, 'Too many arguments')
+
+    for opt, arg in opts:
+        if opt in ('-h', '--help'):
+            usage(0)
+        elif opt in ('-c', '--cipher'):
+            ciphermodule = arg
+        elif opt in ('-l', '--aslong'):
+            aslong = 1
+
+    # ugly hack to force __import__ to give us the end-path module
+    module = __import__('Crypto.Cipher.'+ciphermodule, None, None, ['new'])
+
+    a = AllOrNothing(module)
+    print 'Original text:\n=========='
+    print __doc__
+    print '=========='
+    msgblocks = a.digest(__doc__)
+    print 'message blocks:'
+    for i, blk in map(None, range(len(msgblocks)), msgblocks):
+        # base64 adds a trailing newline
+        print '    %3d' % i,
+        if aslong:
+            print bytes_to_long(blk)
+        else:
+            print base64.encodestring(blk)[:-1]
+    #
+    # get a new undigest-only object so there's no leakage
+    b = AllOrNothing(module)
+    text = b.undigest(msgblocks)
+    if text == __doc__:
+        print 'They match!'
+    else:
+        print 'They differ!'

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/Chaffing.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/Chaffing.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,229 @@
+"""This file implements the chaffing algorithm.
+
+Winnowing and chaffing is a technique for enhancing privacy without requiring
+strong encryption.  In short, the technique takes a set of authenticated
+message blocks (the wheat) and adds a number of chaff blocks which have
+randomly chosen data and MAC fields.  This means that to an adversary, the
+chaff blocks look as valid as the wheat blocks, and so the authentication
+would have to be performed on every block.  By tailoring the number of chaff
+blocks added to the message, the sender can make breaking the message
+computationally infeasible.  There are many other interesting properties of
+the winnow/chaff technique.
+
+For example, say Alice is sending a message to Bob.  She packetizes the
+message and performs an all-or-nothing transformation on the packets.  Then
+she authenticates each packet with a message authentication code (MAC).  The
+MAC is a hash of the data packet, and there is a secret key which she must
+share with Bob (key distribution is an exercise left to the reader).  She then
+adds a serial number to each packet, and sends the packets to Bob.
+
+Bob receives the packets, and using the shared secret authentication key,
+authenticates the MACs for each packet.  Those packets that have bad MACs are
+simply discarded.  The remainder are sorted by serial number, and passed
+through the reverse all-or-nothing transform.  The transform means that an
+eavesdropper (say Eve) must acquire all the packets before any of the data can
+be read.  If even one packet is missing, the data is useless.
+
+There's one twist: by adding chaff packets, Alice and Bob can make Eve's job
+much harder, since Eve now has to break the shared secret key, or try every
+combination of wheat and chaff packet to read any of the message.  The cool
+thing is that Bob doesn't need to add any additional code; the chaff packets
+are already filtered out because their MACs don't match (in all likelihood --
+since the data and MACs for the chaff packets are randomly chosen it is
+possible, but very unlikely that a chaff MAC will match the chaff data).  And
+Alice need not even be the party adding the chaff!  She could be completely
+unaware that a third party, say Charles, is adding chaff packets to her
+messages as they are transmitted.
+
+For more information on winnowing and chaffing see this paper:
+
+Ronald L. Rivest, "Chaffing and Winnowing: Confidentiality without Encryption"
+http://theory.lcs.mit.edu/~rivest/chaffing.txt
+
+"""
+
+__revision__ = "$Id: Chaffing.py,v 1.7 2003/02/28 15:23:21 akuchling Exp $"
+
+from Crypto.Util.number import bytes_to_long
+
+class Chaff:
+    """Class implementing the chaff adding algorithm.
+
+    Methods for subclasses:
+
+            _randnum(size):
+                Returns a randomly generated number with a byte-length equal
+                to size.  Subclasses can use this to implement better random
+                data and MAC generating algorithms.  The default algorithm is
+                probably not very cryptographically secure.  It is most
+                important that the chaff data does not contain any patterns
+                that can be used to discern it from wheat data without running
+                the MAC.
+
+    """
+
+    def __init__(self, factor=1.0, blocksper=1):
+        """Chaff(factor:float, blocksper:int)
+
+        factor is the number of message blocks to add chaff to,
+        expressed as a percentage between 0.0 and 1.0.  blocksper is
+        the number of chaff blocks to include for each block being
+        chaffed.  Thus the defaults add one chaff block to every
+        message block.  By changing the defaults, you can adjust how
+        computationally difficult it could be for an adversary to
+        brute-force crack the message.  The difficulty is expressed
+        as:
+
+            pow(blocksper, int(factor * number-of-blocks))
+
+        For ease of implementation, when factor < 1.0, only the first
+        int(factor*number-of-blocks) message blocks are chaffed.
+        """
+
+        if not (0.0<=factor<=1.0):
+            raise ValueError, "'factor' must be between 0.0 and 1.0"
+        if blocksper < 0:
+            raise ValueError, "'blocksper' must be zero or more"
+
+        self.__factor = factor
+        self.__blocksper = blocksper
+
+
+    def chaff(self, blocks):
+        """chaff( [(serial-number:int, data:string, MAC:string)] )
+        : [(int, string, string)]
+
+        Add chaff to message blocks.  blocks is a list of 3-tuples of the
+        form (serial-number, data, MAC).
+
+        Chaff is created by choosing a random number of the same
+        byte-length as data, and another random number of the same
+        byte-length as MAC.  The message block's serial number is
+        placed on the chaff block and all the packet's chaff blocks
+        are randomly interspersed with the single wheat block.  This
+        method then returns a list of 3-tuples of the same form.
+        Chaffed blocks will contain multiple instances of 3-tuples
+        with the same serial number, but the only way to figure out
+        which blocks are wheat and which are chaff is to perform the
+        MAC hash and compare values.
+        """
+
+        chaffedblocks = []
+
+        # count is the number of blocks to add chaff to.  blocksper is the
+        # number of chaff blocks to add per message block that is being
+        # chaffed.
+        count = len(blocks) * self.__factor
+        blocksper = range(self.__blocksper)
+        for i, wheat in map(None, range(len(blocks)), blocks):
+            # it shouldn't matter which of the n blocks we add chaff to, so for
+            # ease of implementation, we'll just add them to the first count
+            # blocks
+            if i < count:
+                serial, data, mac = wheat
+                datasize = len(data)
+                macsize = len(mac)
+                addwheat = 1
+                # add chaff to this block
+                for j in blocksper:
+                    import sys
+                    chaffdata = self._randnum(datasize)
+                    chaffmac = self._randnum(macsize)
+                    chaff = (serial, chaffdata, chaffmac)
+                    # mix up the order, if the 5th bit is on then put the
+                    # wheat on the list
+                    if addwheat and bytes_to_long(self._randnum(16)) & 0x40:
+                        chaffedblocks.append(wheat)
+                        addwheat = 0
+                    chaffedblocks.append(chaff)
+                if addwheat:
+                    chaffedblocks.append(wheat)
+            else:
+                # just add the wheat
+                chaffedblocks.append(wheat)
+        return chaffedblocks
+
+    def _randnum(self, size):
+        # TBD: Not a very secure algorithm.
+        # TBD: size * 2 to work around possible bug in RandomPool
+        from Crypto.Util import randpool
+        import time
+        pool = randpool.RandomPool(size * 2)
+        while size > pool.entropy:
+            pass
+
+        # we now have enough entropy in the pool to get size bytes of random
+        # data... well, probably
+        return pool.get_bytes(size)
+
+
+
+if __name__ == '__main__':
+    text = """\
+We hold these truths to be self-evident, that all men are created equal, that
+they are endowed by their Creator with certain unalienable Rights, that among
+these are Life, Liberty, and the pursuit of Happiness. That to secure these
+rights, Governments are instituted among Men, deriving their just powers from
+the consent of the governed. That whenever any Form of Government becomes
+destructive of these ends, it is the Right of the People to alter or to
+abolish it, and to institute new Government, laying its foundation on such
+principles and organizing its powers in such form, as to them shall seem most
+likely to effect their Safety and Happiness.
+"""
+    print 'Original text:\n=========='
+    print text
+    print '=========='
+
+    # first transform the text into packets
+    blocks = [] ; size = 40
+    for i in range(0, len(text), size):
+        blocks.append( text[i:i+size] )
+
+    # now get MACs for all the text blocks.  The key is obvious...
+    print 'Calculating MACs...'
+    from Crypto.Hash import HMAC, SHA
+    key = 'Jefferson'
+    macs = [HMAC.new(key, block, digestmod=SHA).digest()
+            for block in blocks]
+
+    assert len(blocks) == len(macs)
+
+    # put these into a form acceptable as input to the chaffing procedure
+    source = []
+    m = map(None, range(len(blocks)), blocks, macs)
+    print m
+    for i, data, mac in m:
+        source.append((i, data, mac))
+
+    # now chaff these
+    print 'Adding chaff...'
+    c = Chaff(factor=0.5, blocksper=2)
+    chaffed = c.chaff(source)
+
+    from base64 import encodestring
+
+    # print the chaffed message blocks.  meanwhile, separate the wheat from
+    # the chaff
+
+    wheat = []
+    print 'chaffed message blocks:'
+    for i, data, mac in chaffed:
+        # do the authentication
+        h = HMAC.new(key, data, digestmod=SHA)
+        pmac = h.digest()
+        if pmac == mac:
+            tag = '-->'
+            wheat.append(data)
+        else:
+            tag = '   '
+        # base64 adds a trailing newline
+        print tag, '%3d' % i, \
+              repr(data), encodestring(mac)[:-1]
+
+    # now decode the message packets and check it against the original text
+    print 'Undigesting wheat...'
+    newtext = "".join(wheat)
+    if newtext == text:
+        print 'They match!'
+    else:
+        print 'They differ!'

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Protocol/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,17 @@
+
+"""Cryptographic protocols
+
+Implements various cryptographic protocols.  (Don't expect to find
+network protocols here.)
+
+Crypto.Protocol.AllOrNothing   Transforms a message into a set of message
+                               blocks, such that the blocks can be
+                               recombined to get the message back.
+
+Crypto.Protocol.Chaffing       Takes a set of authenticated message blocks
+                               (the wheat) and adds a number of
+                               randomly generated blocks (the chaff).
+"""
+
+__all__ = ['AllOrNothing', 'Chaffing']
+__revision__ = "$Id: __init__.py,v 1.4 2003/02/28 15:23:21 akuchling Exp $"

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/DSA.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/DSA.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,238 @@
+
+#
+#   DSA.py : Digital Signature Algorithm
+#
+#  Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.  This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: DSA.py,v 1.16 2004/05/06 12:52:54 akuchling Exp $"
+
+from Crypto.PublicKey.pubkey import *
+from Crypto.Util import number
+from Crypto.Util.number import bytes_to_long, long_to_bytes
+from Crypto.Hash import SHA
+
+try:
+    from Crypto.PublicKey import _fastmath
+except ImportError:
+    _fastmath = None
+
+class error (Exception):
+    pass
+
+def generateQ(randfunc):
+    S=randfunc(20)
+    hash1=SHA.new(S).digest()
+    hash2=SHA.new(long_to_bytes(bytes_to_long(S)+1)).digest()
+    q = bignum(0)
+    for i in range(0,20):
+        c=ord(hash1[i])^ord(hash2[i])
+        if i==0:
+            c=c | 128
+        if i==19:
+            c= c | 1
+        q=q*256+c
+    while (not isPrime(q)):
+        q=q+2
+    if pow(2,159L) < q < pow(2,160L):
+        return S, q
+    raise error, 'Bad q value generated'
+
+def generate(bits, randfunc, progress_func=None):
+    """generate(bits:int, randfunc:callable, progress_func:callable)
+
+    Generate a DSA key of length 'bits', using 'randfunc' to get
+    random data and 'progress_func', if present, to display
+    the progress of the key generation.
+    """
+
+    if bits<160:
+        raise error, 'Key length <160 bits'
+    obj=DSAobj()
+    # Generate string S and prime q
+    if progress_func:
+        progress_func('p,q\n')
+    while (1):
+        S, obj.q = generateQ(randfunc)
+        n=(bits-1)/160
+        C, N, V = 0, 2, {}
+        b=(obj.q >> 5) & 15
+        powb=pow(bignum(2), b)
+        powL1=pow(bignum(2), bits-1)
+        while C<4096:
+            for k in range(0, n+1):
+                V[k]=bytes_to_long(SHA.new(S+str(N)+str(k)).digest())
+            W=V[n] % powb
+            for k in range(n-1, -1, -1):
+                W=(W<<160L)+V[k]
+            X=W+powL1
+            p=X-(X%(2*obj.q)-1)
+            if powL1<=p and isPrime(p):
+                break
+            C, N = C+1, N+n+1
+        if C<4096:
+            break
+        if progress_func:
+            progress_func('4096 multiples failed\n')
+
+    obj.p = p
+    power=(p-1)/obj.q
+    if progress_func:
+        progress_func('h,g\n')
+    while (1):
+        h=bytes_to_long(randfunc(bits)) % (p-1)
+        g=pow(h, power, p)
+        if 1<h<p-1 and g>1:
+            break
+    obj.g=g
+    if progress_func:
+        progress_func('x,y\n')
+    while (1):
+        x=bytes_to_long(randfunc(20))
+        if 0 < x < obj.q:
+            break
+    obj.x, obj.y = x, pow(g, x, p)
+    return obj
+
+def construct(tuple):
+    """construct(tuple:(long,long,long,long)|(long,long,long,long,long)):DSAobj
+    Construct a DSA object from a 4- or 5-tuple of numbers.
+    """
+    obj=DSAobj()
+    if len(tuple) not in [4,5]:
+        raise error, 'argument for construct() wrong length'
+    for i in range(len(tuple)):
+        field = obj.keydata[i]
+        setattr(obj, field, tuple[i])
+    return obj
+
+class DSAobj(pubkey):
+    keydata=['y', 'g', 'p', 'q', 'x']
+
+    def _encrypt(self, s, Kstr):
+        raise error, 'DSA algorithm cannot encrypt data'
+
+    def _decrypt(self, s):
+        raise error, 'DSA algorithm cannot decrypt data'
+
+    def _sign(self, M, K):
+        if (K<2 or self.q<=K):
+            raise error, 'K is not between 2 and q'
+        r=pow(self.g, K, self.p) % self.q
+        s=(inverse(K, self.q)*(M+self.x*r)) % self.q
+        return (r,s)
+
+    def _verify(self, M, sig):
+        r, s = sig
+        if r<=0 or r>=self.q or s<=0 or s>=self.q:
+            return 0
+        w=inverse(s, self.q)
+        u1, u2 = (M*w) % self.q, (r*w) % self.q
+        v1 = pow(self.g, u1, self.p)
+        v2 = pow(self.y, u2, self.p)
+        v = ((v1*v2) % self.p)
+        v = v % self.q
+        if v==r:
+            return 1
+        return 0
+
+    def size(self):
+        "Return the maximum number of bits that can be handled by this key."
+        return number.size(self.p) - 1
+
+    def has_private(self):
+        """Return a Boolean denoting whether the object contains
+        private components."""
+        if hasattr(self, 'x'):
+            return 1
+        else:
+            return 0
+
+    def can_sign(self):
+        """Return a Boolean value recording whether this algorithm can generate signatures."""
+        return 1
+
+    def can_encrypt(self):
+        """Return a Boolean value recording whether this algorithm can encrypt data."""
+        return 0
+
+    def publickey(self):
+        """Return a new key object containing only the public information."""
+        return construct((self.y, self.g, self.p, self.q))
+
+object=DSAobj
+
+generate_py = generate
+construct_py = construct
+
+class DSAobj_c(pubkey):
+    keydata = ['y', 'g', 'p', 'q', 'x']
+
+    def __init__(self, key):
+        self.key = key
+
+    def __getattr__(self, attr):
+        if attr in self.keydata:
+            return getattr(self.key, attr)
+        else:
+            if self.__dict__.has_key(attr):
+                self.__dict__[attr]
+            else:
+                raise AttributeError, '%s instance has no attribute %s' % (self.__class__, attr)
+
+    def __getstate__(self):
+        d = {}
+        for k in self.keydata:
+            if hasattr(self.key, k):
+                d[k]=getattr(self.key, k)
+        return d
+
+    def __setstate__(self, state):
+        y,g,p,q = state['y'], state['g'], state['p'], state['q']
+        if not state.has_key('x'):
+            self.key = _fastmath.dsa_construct(y,g,p,q)
+        else:
+            x = state['x']
+            self.key = _fastmath.dsa_construct(y,g,p,q,x)
+
+    def _sign(self, M, K):
+        return self.key._sign(M, K)
+
+    def _verify(self, M, (r, s)):
+        return self.key._verify(M, r, s)
+
+    def size(self):
+        return self.key.size()
+
+    def has_private(self):
+        return self.key.has_private()
+
+    def publickey(self):
+        return construct_c((self.key.y, self.key.g, self.key.p, self.key.q))
+
+    def can_sign(self):
+        return 1
+
+    def can_encrypt(self):
+        return 0
+
+def generate_c(bits, randfunc, progress_func=None):
+    obj = generate_py(bits, randfunc, progress_func)
+    y,g,p,q,x = obj.y, obj.g, obj.p, obj.q, obj.x
+    return construct_c((y,g,p,q,x))
+
+def construct_c(tuple):
+    key = apply(_fastmath.dsa_construct, tuple)
+    return DSAobj_c(key)
+
+if _fastmath:
+    #print "using C version of DSA"
+    generate = generate_c
+    construct = construct_c
+    error = _fastmath.error

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/ElGamal.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/ElGamal.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,132 @@
+#
+#   ElGamal.py : ElGamal encryption/decryption and signatures
+#
+#  Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.  This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: ElGamal.py,v 1.9 2003/04/04 19:44:26 akuchling Exp $"
+
+from Crypto.PublicKey.pubkey import *
+from Crypto.Util import number
+
+class error (Exception):
+    pass
+
+# Generate an ElGamal key with N bits
+def generate(bits, randfunc, progress_func=None):
+    """generate(bits:int, randfunc:callable, progress_func:callable)
+
+    Generate an ElGamal key of length 'bits', using 'randfunc' to get
+    random data and 'progress_func', if present, to display
+    the progress of the key generation.
+    """
+    obj=ElGamalobj()
+    # Generate prime p
+    if progress_func:
+        progress_func('p\n')
+    obj.p=bignum(getPrime(bits, randfunc))
+    # Generate random number g
+    if progress_func:
+        progress_func('g\n')
+    size=bits-1-(ord(randfunc(1)) & 63) # g will be from 1--64 bits smaller than p
+    if size<1:
+        size=bits-1
+    while (1):
+        obj.g=bignum(getPrime(size, randfunc))
+        if obj.g < obj.p:
+            break
+        size=(size+1) % bits
+        if size==0:
+            size=4
+    # Generate random number x
+    if progress_func:
+        progress_func('x\n')
+    while (1):
+        size=bits-1-ord(randfunc(1)) # x will be from 1 to 256 bits smaller than p
+        if size>2:
+            break
+    while (1):
+        obj.x=bignum(getPrime(size, randfunc))
+        if obj.x < obj.p:
+            break
+        size = (size+1) % bits
+        if size==0:
+            size=4
+    if progress_func:
+        progress_func('y\n')
+    obj.y = pow(obj.g, obj.x, obj.p)
+    return obj
+
+def construct(tuple):
+    """construct(tuple:(long,long,long,long)|(long,long,long,long,long)))
+             : ElGamalobj
+    Construct an ElGamal key from a 3- or 4-tuple of numbers.
+    """
+
+    obj=ElGamalobj()
+    if len(tuple) not in [3,4]:
+        raise error, 'argument for construct() wrong length'
+    for i in range(len(tuple)):
+        field = obj.keydata[i]
+        setattr(obj, field, tuple[i])
+    return obj
+
+class ElGamalobj(pubkey):
+    keydata=['p', 'g', 'y', 'x']
+
+    def _encrypt(self, M, K):
+        a=pow(self.g, K, self.p)
+        b=( M*pow(self.y, K, self.p) ) % self.p
+        return ( a,b )
+
+    def _decrypt(self, M):
+        if (not hasattr(self, 'x')):
+            raise error, 'Private key not available in this object'
+        ax=pow(M[0], self.x, self.p)
+        plaintext=(M[1] * inverse(ax, self.p ) ) % self.p
+        return plaintext
+
+    def _sign(self, M, K):
+        if (not hasattr(self, 'x')):
+            raise error, 'Private key not available in this object'
+        p1=self.p-1
+        if (GCD(K, p1)!=1):
+            raise error, 'Bad K value: GCD(K,p-1)!=1'
+        a=pow(self.g, K, self.p)
+        t=(M-self.x*a) % p1
+        while t<0: t=t+p1
+        b=(t*inverse(K, p1)) % p1
+        return (a, b)
+
+    def _verify(self, M, sig):
+        v1=pow(self.y, sig[0], self.p)
+        v1=(v1*pow(sig[0], sig[1], self.p)) % self.p
+        v2=pow(self.g, M, self.p)
+        if v1==v2:
+            return 1
+        return 0
+
+    def size(self):
+        "Return the maximum number of bits that can be handled by this key."
+        return number.size(self.p) - 1
+
+    def has_private(self):
+        """Return a Boolean denoting whether the object contains
+        private components."""
+        if hasattr(self, 'x'):
+            return 1
+        else:
+            return 0
+
+    def publickey(self):
+        """Return a new key object containing only the public information."""
+        return construct((self.p, self.g, self.y))
+
+
+object=ElGamalobj

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/RSA.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/RSA.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,256 @@
+#
+#   RSA.py : RSA encryption/decryption
+#
+#  Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.  This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: RSA.py,v 1.20 2004/05/06 12:52:54 akuchling Exp $"
+
+from Crypto.PublicKey import pubkey
+from Crypto.Util import number
+
+try:
+    from Crypto.PublicKey import _fastmath
+except ImportError:
+    _fastmath = None
+
+class error (Exception):
+    pass
+
+def generate(bits, randfunc, progress_func=None):
+    """generate(bits:int, randfunc:callable, progress_func:callable)
+
+    Generate an RSA key of length 'bits', using 'randfunc' to get
+    random data and 'progress_func', if present, to display
+    the progress of the key generation.
+    """
+    obj=RSAobj()
+
+    # Generate the prime factors of n
+    if progress_func:
+        progress_func('p,q\n')
+    p = q = 1L
+    while number.size(p*q) < bits:
+        p = pubkey.getPrime(bits/2, randfunc)
+        q = pubkey.getPrime(bits/2, randfunc)
+
+    # p shall be smaller than q (for calc of u)
+    if p > q:
+        (p, q)=(q, p)
+    obj.p = p
+    obj.q = q
+
+    if progress_func:
+        progress_func('u\n')
+    obj.u = pubkey.inverse(obj.p, obj.q)
+    obj.n = obj.p*obj.q
+
+    obj.e = 65537L
+    if progress_func:
+        progress_func('d\n')
+    obj.d=pubkey.inverse(obj.e, (obj.p-1)*(obj.q-1))
+
+    assert bits <= 1+obj.size(), "Generated key is too small"
+
+    return obj
+
+def construct(tuple):
+    """construct(tuple:(long,) : RSAobj
+    Construct an RSA object from a 2-, 3-, 5-, or 6-tuple of numbers.
+    """
+
+    obj=RSAobj()
+    if len(tuple) not in [2,3,5,6]:
+        raise error, 'argument for construct() wrong length'
+    for i in range(len(tuple)):
+        field = obj.keydata[i]
+        setattr(obj, field, tuple[i])
+    if len(tuple) >= 5:
+        # Ensure p is smaller than q 
+        if obj.p>obj.q:
+            (obj.p, obj.q)=(obj.q, obj.p)
+
+    if len(tuple) == 5:
+        # u not supplied, so we're going to have to compute it.
+        obj.u=pubkey.inverse(obj.p, obj.q)
+
+    return obj
+
+class RSAobj(pubkey.pubkey):
+    keydata = ['n', 'e', 'd', 'p', 'q', 'u']
+    def _encrypt(self, plaintext, K=''):
+        if self.n<=plaintext:
+            raise error, 'Plaintext too large'
+        return (pow(plaintext, self.e, self.n),)
+
+    def _decrypt(self, ciphertext):
+        if (not hasattr(self, 'd')):
+            raise error, 'Private key not available in this object'
+        if self.n<=ciphertext[0]:
+            raise error, 'Ciphertext too large'
+        return pow(ciphertext[0], self.d, self.n)
+
+    def _sign(self, M, K=''):
+        return (self._decrypt((M,)),)
+
+    def _verify(self, M, sig):
+        m2=self._encrypt(sig[0])
+        if m2[0]==M:
+            return 1
+        else: return 0
+
+    def _blind(self, M, B):
+        tmp = pow(B, self.e, self.n)
+        return (M * tmp) % self.n
+
+    def _unblind(self, M, B):
+        tmp = pubkey.inverse(B, self.n)
+        return  (M * tmp) % self.n
+
+    def can_blind (self):
+        """can_blind() : bool
+        Return a Boolean value recording whether this algorithm can
+        blind data.  (This does not imply that this
+        particular key object has the private information required to
+        to blind a message.)
+        """
+        return 1
+
+    def size(self):
+        """size() : int
+        Return the maximum number of bits that can be handled by this key.
+        """
+        return number.size(self.n) - 1
+
+    def has_private(self):
+        """has_private() : bool
+        Return a Boolean denoting whether the object contains
+        private components.
+        """
+        if hasattr(self, 'd'):
+            return 1
+        else: return 0
+
+    def publickey(self):
+        """publickey(): RSAobj
+        Return a new key object containing only the public key information.
+        """
+        return construct((self.n, self.e))
+
+class RSAobj_c(pubkey.pubkey):
+    keydata = ['n', 'e', 'd', 'p', 'q', 'u']
+
+    def __init__(self, key):
+        self.key = key
+
+    def __getattr__(self, attr):
+        if attr in self.keydata:
+            return getattr(self.key, attr)
+        else:
+            if self.__dict__.has_key(attr):
+                self.__dict__[attr]
+            else:
+                raise AttributeError, '%s instance has no attribute %s' % (self.__class__, attr)
+
+    def __getstate__(self):
+        d = {}
+        for k in self.keydata:
+            if hasattr(self.key, k):
+                d[k]=getattr(self.key, k)
+        return d
+
+    def __setstate__(self, state):
+        n,e = state['n'], state['e']
+        if not state.has_key('d'):
+            self.key = _fastmath.rsa_construct(n,e)
+        else:
+            d = state['d']
+            if not state.has_key('q'):
+                self.key = _fastmath.rsa_construct(n,e,d)
+            else:
+                p, q, u = state['p'], state['q'], state['u']
+                self.key = _fastmath.rsa_construct(n,e,d,p,q,u)
+
+    def _encrypt(self, plain, K):
+        return (self.key._encrypt(plain),)
+
+    def _decrypt(self, cipher):
+        return self.key._decrypt(cipher[0])
+
+    def _sign(self, M, K):
+        return (self.key._sign(M),)
+
+    def _verify(self, M, sig):
+        return self.key._verify(M, sig[0])
+
+    def _blind(self, M, B):
+        return self.key._blind(M, B)
+
+    def _unblind(self, M, B):
+        return self.key._unblind(M, B)
+
+    def can_blind (self):
+        return 1
+
+    def size(self):
+        return self.key.size()
+
+    def has_private(self):
+        return self.key.has_private()
+
+    def publickey(self):
+        return construct_c((self.key.n, self.key.e))
+
+def generate_c(bits, randfunc, progress_func = None):
+    # Generate the prime factors of n
+    if progress_func:
+        progress_func('p,q\n')
+
+    p = q = 1L
+    while number.size(p*q) < bits:
+        p = pubkey.getPrime(bits/2, randfunc)
+        q = pubkey.getPrime(bits/2, randfunc)
+
+    # p shall be smaller than q (for calc of u)
+    if p > q:
+        (p, q)=(q, p)
+    if progress_func:
+        progress_func('u\n')
+    u=pubkey.inverse(p, q)
+    n=p*q
+
+    e = 65537L
+    if progress_func:
+        progress_func('d\n')
+    d=pubkey.inverse(e, (p-1)*(q-1))
+    key = _fastmath.rsa_construct(n,e,d,p,q,u)
+    obj = RSAobj_c(key)
+
+##    print p
+##    print q
+##    print number.size(p), number.size(q), number.size(q*p),
+##    print obj.size(), bits
+    assert bits <= 1+obj.size(), "Generated key is too small"
+    return obj
+
+
+def construct_c(tuple):
+    key = apply(_fastmath.rsa_construct, tuple)
+    return RSAobj_c(key)
+
+object = RSAobj
+
+generate_py = generate
+construct_py = construct
+
+if _fastmath:
+    #print "using C version of RSA"
+    generate = generate_c
+    construct = construct_c
+    error = _fastmath.error

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,17 @@
+"""Public-key encryption and signature algorithms.
+
+Public-key encryption uses two different keys, one for encryption and
+one for decryption.  The encryption key can be made public, and the
+decryption key is kept private.  Many public-key algorithms can also
+be used to sign messages, and some can *only* be used for signatures.
+
+Crypto.PublicKey.DSA      Digital Signature Algorithm. (Signature only)
+Crypto.PublicKey.ElGamal  (Signing and encryption)
+Crypto.PublicKey.RSA      (Signing, encryption, and blinding)
+Crypto.PublicKey.qNEW     (Signature only)
+
+"""
+
+__all__ = ['RSA', 'DSA', 'ElGamal', 'qNEW']
+__revision__ = "$Id: __init__.py,v 1.4 2003/04/03 20:27:13 akuchling Exp $"
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/pubkey.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/pubkey.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,172 @@
+#
+#   pubkey.py : Internal functions for public key operations
+#
+#  Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.  This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: pubkey.py,v 1.11 2003/04/03 20:36:14 akuchling Exp $"
+
+import types, warnings
+from Crypto.Util.number import *
+
+# Basic public key class
+class pubkey:
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """To keep key objects platform-independent, the key data is
+        converted to standard Python long integers before being
+        written out.  It will then be reconverted as necessary on
+        restoration."""
+        d=self.__dict__
+        for key in self.keydata:
+            if d.has_key(key): d[key]=long(d[key])
+        return d
+
+    def __setstate__(self, d):
+        """On unpickling a key object, the key data is converted to the big
+number representation being used, whether that is Python long
+integers, MPZ objects, or whatever."""
+        for key in self.keydata:
+            if d.has_key(key): self.__dict__[key]=bignum(d[key])
+
+    def encrypt(self, plaintext, K):
+        """encrypt(plaintext:string|long, K:string|long) : tuple
+        Encrypt the string or integer plaintext.  K is a random
+        parameter required by some algorithms.
+        """
+        wasString=0
+        if isinstance(plaintext, types.StringType):
+            plaintext=bytes_to_long(plaintext) ; wasString=1
+        if isinstance(K, types.StringType):
+            K=bytes_to_long(K)
+        ciphertext=self._encrypt(plaintext, K)
+        if wasString: return tuple(map(long_to_bytes, ciphertext))
+        else: return ciphertext
+
+    def decrypt(self, ciphertext):
+        """decrypt(ciphertext:tuple|string|long): string
+        Decrypt 'ciphertext' using this key.
+        """
+        wasString=0
+        if not isinstance(ciphertext, types.TupleType):
+            ciphertext=(ciphertext,)
+        if isinstance(ciphertext[0], types.StringType):
+            ciphertext=tuple(map(bytes_to_long, ciphertext)) ; wasString=1
+        plaintext=self._decrypt(ciphertext)
+        if wasString: return long_to_bytes(plaintext)
+        else: return plaintext
+
+    def sign(self, M, K):
+        """sign(M : string|long, K:string|long) : tuple
+        Return a tuple containing the signature for the message M.
+        K is a random parameter required by some algorithms.
+        """
+        if (not self.has_private()):
+            raise error, 'Private key not available in this object'
+        if isinstance(M, types.StringType): M=bytes_to_long(M)
+        if isinstance(K, types.StringType): K=bytes_to_long(K)
+        return self._sign(M, K)
+
+    def verify (self, M, signature):
+        """verify(M:string|long, signature:tuple) : bool
+        Verify that the signature is valid for the message M;
+        returns true if the signature checks out.
+        """
+        if isinstance(M, types.StringType): M=bytes_to_long(M)
+        return self._verify(M, signature)
+
+    # alias to compensate for the old validate() name
+    def validate (self, M, signature):
+        warnings.warn("validate() method name is obsolete; use verify()",
+                      DeprecationWarning)
+
+    def blind(self, M, B):
+        """blind(M : string|long, B : string|long) : string|long
+        Blind message M using blinding factor B.
+        """
+        wasString=0
+        if isinstance(M, types.StringType):
+            M=bytes_to_long(M) ; wasString=1
+        if isinstance(B, types.StringType): B=bytes_to_long(B)
+        blindedmessage=self._blind(M, B)
+        if wasString: return long_to_bytes(blindedmessage)
+        else: return blindedmessage
+
+    def unblind(self, M, B):
+        """unblind(M : string|long, B : string|long) : string|long
+        Unblind message M using blinding factor B.
+        """
+        wasString=0
+        if isinstance(M, types.StringType):
+            M=bytes_to_long(M) ; wasString=1
+        if isinstance(B, types.StringType): B=bytes_to_long(B)
+        unblindedmessage=self._unblind(M, B)
+        if wasString: return long_to_bytes(unblindedmessage)
+        else: return unblindedmessage
+
+
+    # The following methods will usually be left alone, except for
+    # signature-only algorithms.  They both return Boolean values
+    # recording whether this key's algorithm can sign and encrypt.
+    def can_sign (self):
+        """can_sign() : bool
+        Return a Boolean value recording whether this algorithm can
+        generate signatures.  (This does not imply that this
+        particular key object has the private information required to
+        to generate a signature.)
+        """
+        return 1
+
+    def can_encrypt (self):
+        """can_encrypt() : bool
+        Return a Boolean value recording whether this algorithm can
+        encrypt data.  (This does not imply that this
+        particular key object has the private information required to
+        to decrypt a message.)
+        """
+        return 1
+
+    def can_blind (self):
+        """can_blind() : bool
+        Return a Boolean value recording whether this algorithm can
+        blind data.  (This does not imply that this
+        particular key object has the private information required to
+        to blind a message.)
+        """
+        return 0
+
+    # The following methods will certainly be overridden by
+    # subclasses.
+
+    def size (self):
+        """size() : int
+        Return the maximum number of bits that can be handled by this key.
+        """
+        return 0
+
+    def has_private (self):
+        """has_private() : bool
+        Return a Boolean denoting whether the object contains
+        private components.
+        """
+        return 0
+
+    def publickey (self):
+        """publickey(): object
+        Return a new key object containing only the public information.
+        """
+        return self
+
+    def __eq__ (self, other):
+        """__eq__(other): 0, 1
+        Compare us to other for equality.
+        """
+        return self.__getstate__() == other.__getstate__()

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/qNEW.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/PublicKey/qNEW.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,170 @@
+#
+#   qNEW.py : The q-NEW signature algorithm.
+#
+#  Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.    This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: qNEW.py,v 1.8 2003/04/04 15:13:35 akuchling Exp $"
+
+from Crypto.PublicKey import pubkey
+from Crypto.Util.number import *
+from Crypto.Hash import SHA
+
+class error (Exception):
+    pass
+
+HASHBITS = 160   # Size of SHA digests
+
+def generate(bits, randfunc, progress_func=None):
+    """generate(bits:int, randfunc:callable, progress_func:callable)
+
+    Generate a qNEW key of length 'bits', using 'randfunc' to get
+    random data and 'progress_func', if present, to display
+    the progress of the key generation.
+    """
+    obj=qNEWobj()
+
+    # Generate prime numbers p and q.  q is a 160-bit prime
+    # number.  p is another prime number (the modulus) whose bit
+    # size is chosen by the caller, and is generated so that p-1
+    # is a multiple of q.
+    #
+    # Note that only a single seed is used to
+    # generate p and q; if someone generates a key for you, you can
+    # use the seed to duplicate the key generation.  This can
+    # protect you from someone generating values of p,q that have
+    # some special form that's easy to break.
+    if progress_func:
+        progress_func('p,q\n')
+    while (1):
+        obj.q = getPrime(160, randfunc)
+        #           assert pow(2, 159L)<obj.q<pow(2, 160L)
+        obj.seed = S = long_to_bytes(obj.q)
+        C, N, V = 0, 2, {}
+        # Compute b and n such that bits-1 = b + n*HASHBITS
+        n= (bits-1) / HASHBITS
+        b= (bits-1) % HASHBITS ; powb=2L << b
+        powL1=pow(long(2), bits-1)
+        while C<4096:
+            # The V array will contain (bits-1) bits of random
+            # data, that are assembled to produce a candidate
+            # value for p.
+            for k in range(0, n+1):
+                V[k]=bytes_to_long(SHA.new(S+str(N)+str(k)).digest())
+            p = V[n] % powb
+            for k in range(n-1, -1, -1):
+                p= (p << long(HASHBITS) )+V[k]
+            p = p+powL1         # Ensure the high bit is set
+
+            # Ensure that p-1 is a multiple of q
+            p = p - (p % (2*obj.q)-1)
+
+            # If p is still the right size, and it's prime, we're done!
+            if powL1<=p and isPrime(p):
+                break
+
+            # Otherwise, increment the counter and try again
+            C, N = C+1, N+n+1
+        if C<4096:
+            break   # Ended early, so exit the while loop
+        if progress_func:
+            progress_func('4096 values of p tried\n')
+
+    obj.p = p
+    power=(p-1)/obj.q
+
+    # Next parameter: g = h**((p-1)/q) mod p, such that h is any
+    # number <p-1, and g>1.  g is kept; h can be discarded.
+    if progress_func:
+        progress_func('h,g\n')
+    while (1):
+        h=bytes_to_long(randfunc(bits)) % (p-1)
+        g=pow(h, power, p)
+        if 1<h<p-1 and g>1:
+            break
+    obj.g=g
+
+    # x is the private key information, and is
+    # just a random number between 0 and q.
+    # y=g**x mod p, and is part of the public information.
+    if progress_func:
+        progress_func('x,y\n')
+    while (1):
+        x=bytes_to_long(randfunc(20))
+        if 0 < x < obj.q:
+            break
+    obj.x, obj.y=x, pow(g, x, p)
+
+    return obj
+
+# Construct a qNEW object
+def construct(tuple):
+    """construct(tuple:(long,long,long,long)|(long,long,long,long,long)
+    Construct a qNEW object from a 4- or 5-tuple of numbers.
+    """
+    obj=qNEWobj()
+    if len(tuple) not in [4,5]:
+        raise error, 'argument for construct() wrong length'
+    for i in range(len(tuple)):
+        field = obj.keydata[i]
+        setattr(obj, field, tuple[i])
+    return obj
+
+class qNEWobj(pubkey.pubkey):
+    keydata=['p', 'q', 'g', 'y', 'x']
+
+    def _sign(self, M, K=''):
+        if (self.q<=K):
+            raise error, 'K is greater than q'
+        if M<0:
+            raise error, 'Illegal value of M (<0)'
+        if M>=pow(2,161L):
+            raise error, 'Illegal value of M (too large)'
+        r=pow(self.g, K, self.p) % self.q
+        s=(K- (r*M*self.x % self.q)) % self.q
+        return (r,s)
+    def _verify(self, M, sig):
+        r, s = sig
+        if r<=0 or r>=self.q or s<=0 or s>=self.q:
+            return 0
+        if M<0:
+            raise error, 'Illegal value of M (<0)'
+        if M<=0 or M>=pow(2,161L):
+            return 0
+        v1 = pow(self.g, s, self.p)
+        v2 = pow(self.y, M*r, self.p)
+        v = ((v1*v2) % self.p)
+        v = v % self.q
+        if v==r:
+            return 1
+        return 0
+
+    def size(self):
+        "Return the maximum number of bits that can be handled by this key."
+        return 160
+
+    def has_private(self):
+        """Return a Boolean denoting whether the object contains
+        private components."""
+        return hasattr(self, 'x')
+
+    def can_sign(self):
+        """Return a Boolean value recording whether this algorithm can generate signatures."""
+        return 1
+
+    def can_encrypt(self):
+        """Return a Boolean value recording whether this algorithm can encrypt data."""
+        return 0
+
+    def publickey(self):
+        """Return a new key object containing only the public information."""
+        return construct((self.p, self.q, self.g, self.y))
+
+object = qNEWobj
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/RFC1751.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/RFC1751.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,342 @@
+#!/usr/local/bin/python
+# rfc1751.py : Converts between 128-bit strings and a human-readable
+# sequence of words, as defined in RFC1751: "A Convention for
+# Human-Readable 128-bit Keys", by Daniel L. McDonald.
+
+__revision__ = "$Id: RFC1751.py,v 1.6 2003/04/04 15:15:10 akuchling Exp $"
+
+
+import string, binascii
+
+binary={0:'0000', 1:'0001', 2:'0010', 3:'0011', 4:'0100', 5:'0101',
+        6:'0110', 7:'0111', 8:'1000', 9:'1001', 10:'1010', 11:'1011',
+        12:'1100', 13:'1101', 14:'1110', 15:'1111'}
+
+def _key2bin(s):
+    "Convert a key into a string of binary digits"
+    kl=map(lambda x: ord(x), s)
+    kl=map(lambda x: binary[x/16]+binary[x&15], kl)
+    return ''.join(kl)
+
+def _extract(key, start, length):
+    """Extract a bitstring from a string of binary digits, and return its
+    numeric value."""
+    k=key[start:start+length]
+    return reduce(lambda x,y: x*2+ord(y)-48, k, 0)
+
+def key_to_english (key):
+    """key_to_english(key:string) : string
+    Transform an arbitrary key into a string containing English words.
+    The key length must be a multiple of 8.
+    """
+    english=''
+    for index in range(0, len(key), 8): # Loop over 8-byte subkeys
+        subkey=key[index:index+8]
+        # Compute the parity of the key
+        skbin=_key2bin(subkey) ; p=0
+        for i in range(0, 64, 2): p=p+_extract(skbin, i, 2)
+        # Append parity bits to the subkey
+        skbin=_key2bin(subkey+chr((p<<6) & 255))
+        for i in range(0, 64, 11):
+            english=english+wordlist[_extract(skbin, i, 11)]+' '
+
+    return english[:-1]                 # Remove the trailing space
+
+def english_to_key (str):
+    """english_to_key(string):string
+    Transform a string into a corresponding key.
+    The string must contain words separated by whitespace; the number
+    of words must be a multiple of 6.
+    """
+
+    L=string.split(string.upper(str)) ; key=''
+    for index in range(0, len(L), 6):
+        sublist=L[index:index+6] ; char=9*[0] ; bits=0
+        for i in sublist:
+            index = wordlist.index(i)
+            shift = (8-(bits+11)%8) %8
+            y = index << shift
+            cl, cc, cr = (y>>16), (y>>8)&0xff, y & 0xff
+            if (shift>5):
+                char[bits/8] = char[bits/8] | cl
+                char[bits/8+1] = char[bits/8+1] | cc
+                char[bits/8+2] = char[bits/8+2] | cr
+            elif shift>-3:
+                char[bits/8] = char[bits/8] | cc
+                char[bits/8+1] = char[bits/8+1] | cr
+            else: char[bits/8] = char[bits/8] | cr
+            bits=bits+11
+        subkey=reduce(lambda x,y:x+chr(y), char, '')
+
+        # Check the parity of the resulting key
+        skbin=_key2bin(subkey)
+        p=0
+        for i in range(0, 64, 2): p=p+_extract(skbin, i, 2)
+        if (p&3) != _extract(skbin, 64, 2):
+            raise ValueError, "Parity error in resulting key"
+        key=key+subkey[0:8]
+    return key
+
+wordlist=[ "A", "ABE", "ACE", "ACT", "AD", "ADA", "ADD",
+   "AGO", "AID", "AIM", "AIR", "ALL", "ALP", "AM", "AMY", "AN", "ANA",
+   "AND", "ANN", "ANT", "ANY", "APE", "APS", "APT", "ARC", "ARE", "ARK",
+   "ARM", "ART", "AS", "ASH", "ASK", "AT", "ATE", "AUG", "AUK", "AVE",
+   "AWE", "AWK", "AWL", "AWN", "AX", "AYE", "BAD", "BAG", "BAH", "BAM",
+   "BAN", "BAR", "BAT", "BAY", "BE", "BED", "BEE", "BEG", "BEN", "BET",
+   "BEY", "BIB", "BID", "BIG", "BIN", "BIT", "BOB", "BOG", "BON", "BOO",
+   "BOP", "BOW", "BOY", "BUB", "BUD", "BUG", "BUM", "BUN", "BUS", "BUT",
+   "BUY", "BY", "BYE", "CAB", "CAL", "CAM", "CAN", "CAP", "CAR", "CAT",
+   "CAW", "COD", "COG", "COL", "CON", "COO", "COP", "COT", "COW", "COY",
+   "CRY", "CUB", "CUE", "CUP", "CUR", "CUT", "DAB", "DAD", "DAM", "DAN",
+   "DAR", "DAY", "DEE", "DEL", "DEN", "DES", "DEW", "DID", "DIE", "DIG",
+   "DIN", "DIP", "DO", "DOE", "DOG", "DON", "DOT", "DOW", "DRY", "DUB",
+   "DUD", "DUE", "DUG", "DUN", "EAR", "EAT", "ED", "EEL", "EGG", "EGO",
+   "ELI", "ELK", "ELM", "ELY", "EM", "END", "EST", "ETC", "EVA", "EVE",
+   "EWE", "EYE", "FAD", "FAN", "FAR", "FAT", "FAY", "FED", "FEE", "FEW",
+   "FIB", "FIG", "FIN", "FIR", "FIT", "FLO", "FLY", "FOE", "FOG", "FOR",
+   "FRY", "FUM", "FUN", "FUR", "GAB", "GAD", "GAG", "GAL", "GAM", "GAP",
+   "GAS", "GAY", "GEE", "GEL", "GEM", "GET", "GIG", "GIL", "GIN", "GO",
+   "GOT", "GUM", "GUN", "GUS", "GUT", "GUY", "GYM", "GYP", "HA", "HAD",
+   "HAL", "HAM", "HAN", "HAP", "HAS", "HAT", "HAW", "HAY", "HE", "HEM",
+   "HEN", "HER", "HEW", "HEY", "HI", "HID", "HIM", "HIP", "HIS", "HIT",
+   "HO", "HOB", "HOC", "HOE", "HOG", "HOP", "HOT", "HOW", "HUB", "HUE",
+   "HUG", "HUH", "HUM", "HUT", "I", "ICY", "IDA", "IF", "IKE", "ILL",
+   "INK", "INN", "IO", "ION", "IQ", "IRA", "IRE", "IRK", "IS", "IT",
+   "ITS", "IVY", "JAB", "JAG", "JAM", "JAN", "JAR", "JAW", "JAY", "JET",
+   "JIG", "JIM", "JO", "JOB", "JOE", "JOG", "JOT", "JOY", "JUG", "JUT",
+   "KAY", "KEG", "KEN", "KEY", "KID", "KIM", "KIN", "KIT", "LA", "LAB",
+   "LAC", "LAD", "LAG", "LAM", "LAP", "LAW", "LAY", "LEA", "LED", "LEE",
+   "LEG", "LEN", "LEO", "LET", "LEW", "LID", "LIE", "LIN", "LIP", "LIT",
+   "LO", "LOB", "LOG", "LOP", "LOS", "LOT", "LOU", "LOW", "LOY", "LUG",
+   "LYE", "MA", "MAC", "MAD", "MAE", "MAN", "MAO", "MAP", "MAT", "MAW",
+   "MAY", "ME", "MEG", "MEL", "MEN", "MET", "MEW", "MID", "MIN", "MIT",
+   "MOB", "MOD", "MOE", "MOO", "MOP", "MOS", "MOT", "MOW", "MUD", "MUG",
+   "MUM", "MY", "NAB", "NAG", "NAN", "NAP", "NAT", "NAY", "NE", "NED",
+   "NEE", "NET", "NEW", "NIB", "NIL", "NIP", "NIT", "NO", "NOB", "NOD",
+   "NON", "NOR", "NOT", "NOV", "NOW", "NU", "NUN", "NUT", "O", "OAF",
+   "OAK", "OAR", "OAT", "ODD", "ODE", "OF", "OFF", "OFT", "OH", "OIL",
+   "OK", "OLD", "ON", "ONE", "OR", "ORB", "ORE", "ORR", "OS", "OTT",
+   "OUR", "OUT", "OVA", "OW", "OWE", "OWL", "OWN", "OX", "PA", "PAD",
+   "PAL", "PAM", "PAN", "PAP", "PAR", "PAT", "PAW", "PAY", "PEA", "PEG",
+   "PEN", "PEP", "PER", "PET", "PEW", "PHI", "PI", "PIE", "PIN", "PIT",
+   "PLY", "PO", "POD", "POE", "POP", "POT", "POW", "PRO", "PRY", "PUB",
+   "PUG", "PUN", "PUP", "PUT", "QUO", "RAG", "RAM", "RAN", "RAP", "RAT",
+   "RAW", "RAY", "REB", "RED", "REP", "RET", "RIB", "RID", "RIG", "RIM",
+   "RIO", "RIP", "ROB", "ROD", "ROE", "RON", "ROT", "ROW", "ROY", "RUB",
+   "RUE", "RUG", "RUM", "RUN", "RYE", "SAC", "SAD", "SAG", "SAL", "SAM",
+   "SAN", "SAP", "SAT", "SAW", "SAY", "SEA", "SEC", "SEE", "SEN", "SET",
+   "SEW", "SHE", "SHY", "SIN", "SIP", "SIR", "SIS", "SIT", "SKI", "SKY",
+   "SLY", "SO", "SOB", "SOD", "SON", "SOP", "SOW", "SOY", "SPA", "SPY",
+   "SUB", "SUD", "SUE", "SUM", "SUN", "SUP", "TAB", "TAD", "TAG", "TAN",
+   "TAP", "TAR", "TEA", "TED", "TEE", "TEN", "THE", "THY", "TIC", "TIE",
+   "TIM", "TIN", "TIP", "TO", "TOE", "TOG", "TOM", "TON", "TOO", "TOP",
+   "TOW", "TOY", "TRY", "TUB", "TUG", "TUM", "TUN", "TWO", "UN", "UP",
+   "US", "USE", "VAN", "VAT", "VET", "VIE", "WAD", "WAG", "WAR", "WAS",
+   "WAY", "WE", "WEB", "WED", "WEE", "WET", "WHO", "WHY", "WIN", "WIT",
+   "WOK", "WON", "WOO", "WOW", "WRY", "WU", "YAM", "YAP", "YAW", "YE",
+   "YEA", "YES", "YET", "YOU", "ABED", "ABEL", "ABET", "ABLE", "ABUT",
+   "ACHE", "ACID", "ACME", "ACRE", "ACTA", "ACTS", "ADAM", "ADDS",
+   "ADEN", "AFAR", "AFRO", "AGEE", "AHEM", "AHOY", "AIDA", "AIDE",
+   "AIDS", "AIRY", "AJAR", "AKIN", "ALAN", "ALEC", "ALGA", "ALIA",
+   "ALLY", "ALMA", "ALOE", "ALSO", "ALTO", "ALUM", "ALVA", "AMEN",
+   "AMES", "AMID", "AMMO", "AMOK", "AMOS", "AMRA", "ANDY", "ANEW",
+   "ANNA", "ANNE", "ANTE", "ANTI", "AQUA", "ARAB", "ARCH", "AREA",
+   "ARGO", "ARID", "ARMY", "ARTS", "ARTY", "ASIA", "ASKS", "ATOM",
+   "AUNT", "AURA", "AUTO", "AVER", "AVID", "AVIS", "AVON", "AVOW",
+   "AWAY", "AWRY", "BABE", "BABY", "BACH", "BACK", "BADE", "BAIL",
+   "BAIT", "BAKE", "BALD", "BALE", "BALI", "BALK", "BALL", "BALM",
+   "BAND", "BANE", "BANG", "BANK", "BARB", "BARD", "BARE", "BARK",
+   "BARN", "BARR", "BASE", "BASH", "BASK", "BASS", "BATE", "BATH",
+   "BAWD", "BAWL", "BEAD", "BEAK", "BEAM", "BEAN", "BEAR", "BEAT",
+   "BEAU", "BECK", "BEEF", "BEEN", "BEER",
+   "BEET", "BELA", "BELL", "BELT", "BEND", "BENT", "BERG", "BERN",
+   "BERT", "BESS", "BEST", "BETA", "BETH", "BHOY", "BIAS", "BIDE",
+   "BIEN", "BILE", "BILK", "BILL", "BIND", "BING", "BIRD", "BITE",
+   "BITS", "BLAB", "BLAT", "BLED", "BLEW", "BLOB", "BLOC", "BLOT",
+   "BLOW", "BLUE", "BLUM", "BLUR", "BOAR", "BOAT", "BOCA", "BOCK",
+   "BODE", "BODY", "BOGY", "BOHR", "BOIL", "BOLD", "BOLO", "BOLT",
+   "BOMB", "BONA", "BOND", "BONE", "BONG", "BONN", "BONY", "BOOK",
+   "BOOM", "BOON", "BOOT", "BORE", "BORG", "BORN", "BOSE", "BOSS",
+   "BOTH", "BOUT", "BOWL", "BOYD", "BRAD", "BRAE", "BRAG", "BRAN",
+   "BRAY", "BRED", "BREW", "BRIG", "BRIM", "BROW", "BUCK", "BUDD",
+   "BUFF", "BULB", "BULK", "BULL", "BUNK", "BUNT", "BUOY", "BURG",
+   "BURL", "BURN", "BURR", "BURT", "BURY", "BUSH", "BUSS", "BUST",
+   "BUSY", "BYTE", "CADY", "CAFE", "CAGE", "CAIN", "CAKE", "CALF",
+   "CALL", "CALM", "CAME", "CANE", "CANT", "CARD", "CARE", "CARL",
+   "CARR", "CART", "CASE", "CASH", "CASK", "CAST", "CAVE", "CEIL",
+   "CELL", "CENT", "CERN", "CHAD", "CHAR", "CHAT", "CHAW", "CHEF",
+   "CHEN", "CHEW", "CHIC", "CHIN", "CHOU", "CHOW", "CHUB", "CHUG",
+   "CHUM", "CITE", "CITY", "CLAD", "CLAM", "CLAN", "CLAW", "CLAY",
+   "CLOD", "CLOG", "CLOT", "CLUB", "CLUE", "COAL", "COAT", "COCA",
+   "COCK", "COCO", "CODA", "CODE", "CODY", "COED", "COIL", "COIN",
+   "COKE", "COLA", "COLD", "COLT", "COMA", "COMB", "COME", "COOK",
+   "COOL", "COON", "COOT", "CORD", "CORE", "CORK", "CORN", "COST",
+   "COVE", "COWL", "CRAB", "CRAG", "CRAM", "CRAY", "CREW", "CRIB",
+   "CROW", "CRUD", "CUBA", "CUBE", "CUFF", "CULL", "CULT", "CUNY",
+   "CURB", "CURD", "CURE", "CURL", "CURT", "CUTS", "DADE", "DALE",
+   "DAME", "DANA", "DANE", "DANG", "DANK", "DARE", "DARK", "DARN",
+   "DART", "DASH", "DATA", "DATE", "DAVE", "DAVY", "DAWN", "DAYS",
+   "DEAD", "DEAF", "DEAL", "DEAN", "DEAR", "DEBT", "DECK", "DEED",
+   "DEEM", "DEER", "DEFT", "DEFY", "DELL", "DENT", "DENY", "DESK",
+   "DIAL", "DICE", "DIED", "DIET", "DIME", "DINE", "DING", "DINT",
+   "DIRE", "DIRT", "DISC", "DISH", "DISK", "DIVE", "DOCK", "DOES",
+   "DOLE", "DOLL", "DOLT", "DOME", "DONE", "DOOM", "DOOR", "DORA",
+   "DOSE", "DOTE", "DOUG", "DOUR", "DOVE", "DOWN", "DRAB", "DRAG",
+   "DRAM", "DRAW", "DREW", "DRUB", "DRUG", "DRUM", "DUAL", "DUCK",
+   "DUCT", "DUEL", "DUET", "DUKE", "DULL", "DUMB", "DUNE", "DUNK",
+   "DUSK", "DUST", "DUTY", "EACH", "EARL", "EARN", "EASE", "EAST",
+   "EASY", "EBEN", "ECHO", "EDDY", "EDEN", "EDGE", "EDGY", "EDIT",
+   "EDNA", "EGAN", "ELAN", "ELBA", "ELLA", "ELSE", "EMIL", "EMIT",
+   "EMMA", "ENDS", "ERIC", "EROS", "EVEN", "EVER", "EVIL", "EYED",
+   "FACE", "FACT", "FADE", "FAIL", "FAIN", "FAIR", "FAKE", "FALL",
+   "FAME", "FANG", "FARM", "FAST", "FATE", "FAWN", "FEAR", "FEAT",
+   "FEED", "FEEL", "FEET", "FELL", "FELT", "FEND", "FERN", "FEST",
+   "FEUD", "FIEF", "FIGS", "FILE", "FILL", "FILM", "FIND", "FINE",
+   "FINK", "FIRE", "FIRM", "FISH", "FISK", "FIST", "FITS", "FIVE",
+   "FLAG", "FLAK", "FLAM", "FLAT", "FLAW", "FLEA", "FLED", "FLEW",
+   "FLIT", "FLOC", "FLOG", "FLOW", "FLUB", "FLUE", "FOAL", "FOAM",
+   "FOGY", "FOIL", "FOLD", "FOLK", "FOND", "FONT", "FOOD", "FOOL",
+   "FOOT", "FORD", "FORE", "FORK", "FORM", "FORT", "FOSS", "FOUL",
+   "FOUR", "FOWL", "FRAU", "FRAY", "FRED", "FREE", "FRET", "FREY",
+   "FROG", "FROM", "FUEL", "FULL", "FUME", "FUND", "FUNK", "FURY",
+   "FUSE", "FUSS", "GAFF", "GAGE", "GAIL", "GAIN", "GAIT", "GALA",
+   "GALE", "GALL", "GALT", "GAME", "GANG", "GARB", "GARY", "GASH",
+   "GATE", "GAUL", "GAUR", "GAVE", "GAWK", "GEAR", "GELD", "GENE",
+   "GENT", "GERM", "GETS", "GIBE", "GIFT", "GILD", "GILL", "GILT",
+   "GINA", "GIRD", "GIRL", "GIST", "GIVE", "GLAD", "GLEE", "GLEN",
+   "GLIB", "GLOB", "GLOM", "GLOW", "GLUE", "GLUM", "GLUT", "GOAD",
+   "GOAL", "GOAT", "GOER", "GOES", "GOLD", "GOLF", "GONE", "GONG",
+   "GOOD", "GOOF", "GORE", "GORY", "GOSH", "GOUT", "GOWN", "GRAB",
+   "GRAD", "GRAY", "GREG", "GREW", "GREY", "GRID", "GRIM", "GRIN",
+   "GRIT", "GROW", "GRUB", "GULF", "GULL", "GUNK", "GURU", "GUSH",
+   "GUST", "GWEN", "GWYN", "HAAG", "HAAS", "HACK", "HAIL", "HAIR",
+   "HALE", "HALF", "HALL", "HALO", "HALT", "HAND", "HANG", "HANK",
+   "HANS", "HARD", "HARK", "HARM", "HART", "HASH", "HAST", "HATE",
+   "HATH", "HAUL", "HAVE", "HAWK", "HAYS", "HEAD", "HEAL", "HEAR",
+   "HEAT", "HEBE", "HECK", "HEED", "HEEL", "HEFT", "HELD", "HELL",
+   "HELM", "HERB", "HERD", "HERE", "HERO", "HERS", "HESS", "HEWN",
+   "HICK", "HIDE", "HIGH", "HIKE", "HILL", "HILT", "HIND", "HINT",
+   "HIRE", "HISS", "HIVE", "HOBO", "HOCK", "HOFF", "HOLD", "HOLE",
+   "HOLM", "HOLT", "HOME", "HONE", "HONK", "HOOD", "HOOF", "HOOK",
+   "HOOT", "HORN", "HOSE", "HOST", "HOUR", "HOVE", "HOWE", "HOWL",
+   "HOYT", "HUCK", "HUED", "HUFF", "HUGE", "HUGH", "HUGO", "HULK",
+   "HULL", "HUNK", "HUNT", "HURD", "HURL", "HURT", "HUSH", "HYDE",
+   "HYMN", "IBIS", "ICON", "IDEA", "IDLE", "IFFY", "INCA", "INCH",
+   "INTO", "IONS", "IOTA", "IOWA", "IRIS", "IRMA", "IRON", "ISLE",
+   "ITCH", "ITEM", "IVAN", "JACK", "JADE", "JAIL", "JAKE", "JANE",
+   "JAVA", "JEAN", "JEFF", "JERK", "JESS", "JEST", "JIBE", "JILL",
+   "JILT", "JIVE", "JOAN", "JOBS", "JOCK", "JOEL", "JOEY", "JOHN",
+   "JOIN", "JOKE", "JOLT", "JOVE", "JUDD", "JUDE", "JUDO", "JUDY",
+   "JUJU", "JUKE", "JULY", "JUNE", "JUNK", "JUNO", "JURY", "JUST",
+   "JUTE", "KAHN", "KALE", "KANE", "KANT", "KARL", "KATE", "KEEL",
+   "KEEN", "KENO", "KENT", "KERN", "KERR", "KEYS", "KICK", "KILL",
+   "KIND", "KING", "KIRK", "KISS", "KITE", "KLAN", "KNEE", "KNEW",
+   "KNIT", "KNOB", "KNOT", "KNOW", "KOCH", "KONG", "KUDO", "KURD",
+   "KURT", "KYLE", "LACE", "LACK", "LACY", "LADY", "LAID", "LAIN",
+   "LAIR", "LAKE", "LAMB", "LAME", "LAND", "LANE", "LANG", "LARD",
+   "LARK", "LASS", "LAST", "LATE", "LAUD", "LAVA", "LAWN", "LAWS",
+   "LAYS", "LEAD", "LEAF", "LEAK", "LEAN", "LEAR", "LEEK", "LEER",
+   "LEFT", "LEND", "LENS", "LENT", "LEON", "LESK", "LESS", "LEST",
+   "LETS", "LIAR", "LICE", "LICK", "LIED", "LIEN", "LIES", "LIEU",
+   "LIFE", "LIFT", "LIKE", "LILA", "LILT", "LILY", "LIMA", "LIMB",
+   "LIME", "LIND", "LINE", "LINK", "LINT", "LION", "LISA", "LIST",
+   "LIVE", "LOAD", "LOAF", "LOAM", "LOAN", "LOCK", "LOFT", "LOGE",
+   "LOIS", "LOLA", "LONE", "LONG", "LOOK", "LOON", "LOOT", "LORD",
+   "LORE", "LOSE", "LOSS", "LOST", "LOUD", "LOVE", "LOWE", "LUCK",
+   "LUCY", "LUGE", "LUKE", "LULU", "LUND", "LUNG", "LURA", "LURE",
+   "LURK", "LUSH", "LUST", "LYLE", "LYNN", "LYON", "LYRA", "MACE",
+   "MADE", "MAGI", "MAID", "MAIL", "MAIN", "MAKE", "MALE", "MALI",
+   "MALL", "MALT", "MANA", "MANN", "MANY", "MARC", "MARE", "MARK",
+   "MARS", "MART", "MARY", "MASH", "MASK", "MASS", "MAST", "MATE",
+   "MATH", "MAUL", "MAYO", "MEAD", "MEAL", "MEAN", "MEAT", "MEEK",
+   "MEET", "MELD", "MELT", "MEMO", "MEND", "MENU", "MERT", "MESH",
+   "MESS", "MICE", "MIKE", "MILD", "MILE", "MILK", "MILL", "MILT",
+   "MIMI", "MIND", "MINE", "MINI", "MINK", "MINT", "MIRE", "MISS",
+   "MIST", "MITE", "MITT", "MOAN", "MOAT", "MOCK", "MODE", "MOLD",
+   "MOLE", "MOLL", "MOLT", "MONA", "MONK", "MONT", "MOOD", "MOON",
+   "MOOR", "MOOT", "MORE", "MORN", "MORT", "MOSS", "MOST", "MOTH",
+   "MOVE", "MUCH", "MUCK", "MUDD", "MUFF", "MULE", "MULL", "MURK",
+   "MUSH", "MUST", "MUTE", "MUTT", "MYRA", "MYTH", "NAGY", "NAIL",
+   "NAIR", "NAME", "NARY", "NASH", "NAVE", "NAVY", "NEAL", "NEAR",
+   "NEAT", "NECK", "NEED", "NEIL", "NELL", "NEON", "NERO", "NESS",
+   "NEST", "NEWS", "NEWT", "NIBS", "NICE", "NICK", "NILE", "NINA",
+   "NINE", "NOAH", "NODE", "NOEL", "NOLL", "NONE", "NOOK", "NOON",
+   "NORM", "NOSE", "NOTE", "NOUN", "NOVA", "NUDE", "NULL", "NUMB",
+   "OATH", "OBEY", "OBOE", "ODIN", "OHIO", "OILY", "OINT", "OKAY",
+   "OLAF", "OLDY", "OLGA", "OLIN", "OMAN", "OMEN", "OMIT", "ONCE",
+   "ONES", "ONLY", "ONTO", "ONUS", "ORAL", "ORGY", "OSLO", "OTIS",
+   "OTTO", "OUCH", "OUST", "OUTS", "OVAL", "OVEN", "OVER", "OWLY",
+   "OWNS", "QUAD", "QUIT", "QUOD", "RACE", "RACK", "RACY", "RAFT",
+   "RAGE", "RAID", "RAIL", "RAIN", "RAKE", "RANK", "RANT", "RARE",
+   "RASH", "RATE", "RAVE", "RAYS", "READ", "REAL", "REAM", "REAR",
+   "RECK", "REED", "REEF", "REEK", "REEL", "REID", "REIN", "RENA",
+   "REND", "RENT", "REST", "RICE", "RICH", "RICK", "RIDE", "RIFT",
+   "RILL", "RIME", "RING", "RINK", "RISE", "RISK", "RITE", "ROAD",
+   "ROAM", "ROAR", "ROBE", "ROCK", "RODE", "ROIL", "ROLL", "ROME",
+   "ROOD", "ROOF", "ROOK", "ROOM", "ROOT", "ROSA", "ROSE", "ROSS",
+   "ROSY", "ROTH", "ROUT", "ROVE", "ROWE", "ROWS", "RUBE", "RUBY",
+   "RUDE", "RUDY", "RUIN", "RULE", "RUNG", "RUNS", "RUNT", "RUSE",
+   "RUSH", "RUSK", "RUSS", "RUST", "RUTH", "SACK", "SAFE", "SAGE",
+   "SAID", "SAIL", "SALE", "SALK", "SALT", "SAME", "SAND", "SANE",
+   "SANG", "SANK", "SARA", "SAUL", "SAVE", "SAYS", "SCAN", "SCAR",
+   "SCAT", "SCOT", "SEAL", "SEAM", "SEAR", "SEAT", "SEED", "SEEK",
+   "SEEM", "SEEN", "SEES", "SELF", "SELL", "SEND", "SENT", "SETS",
+   "SEWN", "SHAG", "SHAM", "SHAW", "SHAY", "SHED", "SHIM", "SHIN",
+   "SHOD", "SHOE", "SHOT", "SHOW", "SHUN", "SHUT", "SICK", "SIDE",
+   "SIFT", "SIGH", "SIGN", "SILK", "SILL", "SILO", "SILT", "SINE",
+   "SING", "SINK", "SIRE", "SITE", "SITS", "SITU", "SKAT", "SKEW",
+   "SKID", "SKIM", "SKIN", "SKIT", "SLAB", "SLAM", "SLAT", "SLAY",
+   "SLED", "SLEW", "SLID", "SLIM", "SLIT", "SLOB", "SLOG", "SLOT",
+   "SLOW", "SLUG", "SLUM", "SLUR", "SMOG", "SMUG", "SNAG", "SNOB",
+   "SNOW", "SNUB", "SNUG", "SOAK", "SOAR", "SOCK", "SODA", "SOFA",
+   "SOFT", "SOIL", "SOLD", "SOME", "SONG", "SOON", "SOOT", "SORE",
+   "SORT", "SOUL", "SOUR", "SOWN", "STAB", "STAG", "STAN", "STAR",
+   "STAY", "STEM", "STEW", "STIR", "STOW", "STUB", "STUN", "SUCH",
+   "SUDS", "SUIT", "SULK", "SUMS", "SUNG", "SUNK", "SURE", "SURF",
+   "SWAB", "SWAG", "SWAM", "SWAN", "SWAT", "SWAY", "SWIM", "SWUM",
+   "TACK", "TACT", "TAIL", "TAKE", "TALE", "TALK", "TALL", "TANK",
+   "TASK", "TATE", "TAUT", "TEAL", "TEAM", "TEAR", "TECH", "TEEM",
+   "TEEN", "TEET", "TELL", "TEND", "TENT", "TERM", "TERN", "TESS",
+   "TEST", "THAN", "THAT", "THEE", "THEM", "THEN", "THEY", "THIN",
+   "THIS", "THUD", "THUG", "TICK", "TIDE", "TIDY", "TIED", "TIER",
+   "TILE", "TILL", "TILT", "TIME", "TINA", "TINE", "TINT", "TINY",
+   "TIRE", "TOAD", "TOGO", "TOIL", "TOLD", "TOLL", "TONE", "TONG",
+   "TONY", "TOOK", "TOOL", "TOOT", "TORE", "TORN", "TOTE", "TOUR",
+   "TOUT", "TOWN", "TRAG", "TRAM", "TRAY", "TREE", "TREK", "TRIG",
+   "TRIM", "TRIO", "TROD", "TROT", "TROY", "TRUE", "TUBA", "TUBE",
+   "TUCK", "TUFT", "TUNA", "TUNE", "TUNG", "TURF", "TURN", "TUSK",
+   "TWIG", "TWIN", "TWIT", "ULAN", "UNIT", "URGE", "USED", "USER",
+   "USES", "UTAH", "VAIL", "VAIN", "VALE", "VARY", "VASE", "VAST",
+   "VEAL", "VEDA", "VEIL", "VEIN", "VEND", "VENT", "VERB", "VERY",
+   "VETO", "VICE", "VIEW", "VINE", "VISE", "VOID", "VOLT", "VOTE",
+   "WACK", "WADE", "WAGE", "WAIL", "WAIT", "WAKE", "WALE", "WALK",
+   "WALL", "WALT", "WAND", "WANE", "WANG", "WANT", "WARD", "WARM",
+   "WARN", "WART", "WASH", "WAST", "WATS", "WATT", "WAVE", "WAVY",
+   "WAYS", "WEAK", "WEAL", "WEAN", "WEAR", "WEED", "WEEK", "WEIR",
+   "WELD", "WELL", "WELT", "WENT", "WERE", "WERT", "WEST", "WHAM",
+   "WHAT", "WHEE", "WHEN", "WHET", "WHOA", "WHOM", "WICK", "WIFE",
+   "WILD", "WILL", "WIND", "WINE", "WING", "WINK", "WINO", "WIRE",
+   "WISE", "WISH", "WITH", "WOLF", "WONT", "WOOD", "WOOL", "WORD",
+   "WORE", "WORK", "WORM", "WORN", "WOVE", "WRIT", "WYNN", "YALE",
+   "YANG", "YANK", "YARD", "YARN", "YAWL", "YAWN", "YEAH", "YEAR",
+   "YELL", "YOGA", "YOKE" ]
+
+if __name__=='__main__':
+    data = [('EB33F77EE73D4053', 'TIDE ITCH SLOW REIN RULE MOT'),
+            ('CCAC2AED591056BE4F90FD441C534766',
+             'RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE'),
+            ('EFF81F9BFBC65350920CDD7416DE8009',
+             'TROD MUTE TAIL WARM CHAR KONG HAAG CITY BORE O TEAL AWL')
+           ]
+
+    for key, words in data:
+        print 'Trying key', key
+        key=binascii.a2b_hex(key)
+        w2=key_to_english(key)
+        if w2!=words:
+            print 'key_to_english fails on key', repr(key), ', producing', str(w2)
+        k2=english_to_key(words)
+        if k2!=key:
+            print 'english_to_key fails on key', repr(key), ', producing', repr(k2)
+
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,16 @@
+"""Miscellaneous modules
+
+Contains useful modules that don't belong into any of the
+other Crypto.* subpackages.
+
+Crypto.Util.number        Number-theoretic functions (primality testing, etc.)
+Crypto.Util.randpool      Random number generation
+Crypto.Util.RFC1751       Converts between 128-bit keys and human-readable
+                          strings of words.
+
+"""
+
+__all__ = ['randpool', 'RFC1751', 'number']
+
+__revision__ = "$Id: __init__.py,v 1.4 2003/02/28 15:26:00 akuchling Exp $"
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/number.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/number.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,201 @@
+#
+#   number.py : Number-theoretic functions
+#
+#  Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.  This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: number.py,v 1.13 2003/04/04 18:21:07 akuchling Exp $"
+
+bignum = long
+try:
+    from Crypto.PublicKey import _fastmath
+except ImportError:
+    _fastmath = None
+
+# Commented out and replaced with faster versions below
+## def long2str(n):
+##     s=''
+##     while n>0:
+##         s=chr(n & 255)+s
+##         n=n>>8
+##     return s
+
+## import types
+## def str2long(s):
+##     if type(s)!=types.StringType: return s   # Integers will be left alone
+##     return reduce(lambda x,y : x*256+ord(y), s, 0L)
+
+def size (N):
+    """size(N:long) : int
+    Returns the size of the number N in bits.
+    """
+    bits, power = 0,1L
+    while N >= power:
+        bits += 1
+        power = power << 1
+    return bits
+
+def getRandomNumber(N, randfunc):
+    """getRandomNumber(N:int, randfunc:callable):long
+    Return an N-bit random number."""
+
+    S = randfunc(N/8)
+    odd_bits = N % 8
+    if odd_bits != 0:
+        char = ord(randfunc(1)) >> (8-odd_bits)
+        S = chr(char) + S
+    value = bytes_to_long(S)
+    value |= 2L ** (N-1)                # Ensure high bit is set
+    assert size(value) >= N
+    return value
+
+def GCD(x,y):
+    """GCD(x:long, y:long): long
+    Return the GCD of x and y.
+    """
+    x = abs(x) ; y = abs(y)
+    while x > 0:
+        x, y = y % x, x
+    return y
+
+def inverse(u, v):
+    """inverse(u:long, u:long):long
+    Return the inverse of u mod v.
+    """
+    u3, v3 = long(u), long(v)
+    u1, v1 = 1L, 0L
+    while v3 > 0:
+        q=u3 / v3
+        u1, v1 = v1, u1 - v1*q
+        u3, v3 = v3, u3 - v3*q
+    while u1<0:
+        u1 = u1 + v
+    return u1
+
+# Given a number of bits to generate and a random generation function,
+# find a prime number of the appropriate size.
+
+def getPrime(N, randfunc):
+    """getPrime(N:int, randfunc:callable):long
+    Return a random N-bit prime number.
+    """
+
+    number=getRandomNumber(N, randfunc) | 1
+    while (not isPrime(number)):
+        number=number+2
+    return number
+
+def isPrime(N):
+    """isPrime(N:long):bool
+    Return true if N is prime.
+    """
+    if N == 1:
+        return 0
+    if N in sieve:
+        return 1
+    for i in sieve:
+        if (N % i)==0:
+            return 0
+
+    # Use the accelerator if available
+    if _fastmath is not None:
+        return _fastmath.isPrime(N)
+
+    # Compute the highest bit that's set in N
+    N1 = N - 1L
+    n = 1L
+    while (n<N):
+        n=n<<1L
+    n = n >> 1L
+
+    # Rabin-Miller test
+    for c in sieve[:7]:
+        a=long(c) ; d=1L ; t=n
+        while (t):  # Iterate over the bits in N1
+            x=(d*d) % N
+            if x==1L and d!=1L and d!=N1:
+                return 0  # Square root of 1 found
+            if N1 & t:
+                d=(x*a) % N
+            else:
+                d=x
+            t = t >> 1L
+        if d!=1L:
+            return 0
+    return 1
+
+# Small primes used for checking primality; these are all the primes
+# less than 256.  This should be enough to eliminate most of the odd
+# numbers before needing to do a Rabin-Miller test at all.
+
+sieve=[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
+       61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127,
+       131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
+       197, 199, 211, 223, 227, 229, 233, 239, 241, 251]
+
+# Improved conversion functions contributed by Barry Warsaw, after
+# careful benchmarking
+
+import struct
+
+def long_to_bytes(n, blocksize=0):
+    """long_to_bytes(n:long, blocksize:int) : string
+    Convert a long integer to a byte string.
+
+    If optional blocksize is given and greater than zero, pad the front of the
+    byte string with binary zeros so that the length is a multiple of
+    blocksize.
+    """
+    # after much testing, this algorithm was deemed to be the fastest
+    s = ''
+    n = long(n)
+    pack = struct.pack
+    while n > 0:
+        s = pack('>I', n & 0xffffffffL) + s
+        n = n >> 32
+    # strip off leading zeros
+    for i in range(len(s)):
+        if s[i] != '\000':
+            break
+    else:
+        # only happens when n == 0
+        s = '\000'
+        i = 0
+    s = s[i:]
+    # add back some pad bytes.  this could be done more efficiently w.r.t. the
+    # de-padding being done above, but sigh...
+    if blocksize > 0 and len(s) % blocksize:
+        s = (blocksize - len(s) % blocksize) * '\000' + s
+    return s
+
+def bytes_to_long(s):
+    """bytes_to_long(string) : long
+    Convert a byte string to a long integer.
+
+    This is (essentially) the inverse of long_to_bytes().
+    """
+    acc = 0L
+    unpack = struct.unpack
+    length = len(s)
+    if length % 4:
+        extra = (4 - length % 4)
+        s = '\000' * extra + s
+        length = length + extra
+    for i in range(0, length, 4):
+        acc = (acc << 32) + unpack('>I', s[i:i+4])[0]
+    return acc
+
+# For backwards compatibility...
+import warnings
+def long2str(n, blocksize=0):
+    warnings.warn("long2str() has been replaced by long_to_bytes()")
+    return long_to_bytes(n, blocksize)
+def str2long(s):
+    warnings.warn("str2long() has been replaced by bytes_to_long()")
+    return bytes_to_long(s)

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/randpool.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/randpool.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,421 @@
+#
+#  randpool.py : Cryptographically strong random number generation
+#
+# Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.  This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: randpool.py,v 1.14 2004/05/06 12:56:54 akuchling Exp $"
+
+import time, array, types, warnings, os.path
+from Crypto.Util.number import long_to_bytes
+try:
+    import Crypto.Util.winrandom as winrandom
+except:
+    winrandom = None
+
+STIRNUM = 3
+
+class RandomPool:
+    """randpool.py : Cryptographically strong random number generation.
+
+    The implementation here is similar to the one in PGP.  To be
+    cryptographically strong, it must be difficult to determine the RNG's
+    output, whether in the future or the past.  This is done by using
+    a cryptographic hash function to "stir" the random data.
+
+    Entropy is gathered in the same fashion as PGP; the highest-resolution
+    clock around is read and the data is added to the random number pool.
+    A conservative estimate of the entropy is then kept.
+
+    If a cryptographically secure random source is available (/dev/urandom
+    on many Unixes, Windows CryptGenRandom on most Windows), then use
+    it.
+
+    Instance Attributes:
+    bits : int
+      Maximum size of pool in bits
+    bytes : int
+      Maximum size of pool in bytes
+    entropy : int
+      Number of bits of entropy in this pool.
+
+    Methods:
+    add_event([s]) : add some entropy to the pool
+    get_bytes(int) : get N bytes of random data
+    randomize([N]) : get N bytes of randomness from external source
+    """
+
+
+    def __init__(self, numbytes = 160, cipher=None, hash=None):
+        if hash is None:
+            from Crypto.Hash import SHA as hash
+
+        # The cipher argument is vestigial; it was removed from
+        # version 1.1 so RandomPool would work even in the limited
+        # exportable subset of the code
+        if cipher is not None:
+            warnings.warn("'cipher' parameter is no longer used")
+
+        if isinstance(hash, types.StringType):
+            # ugly hack to force __import__ to give us the end-path module
+            hash = __import__('Crypto.Hash.'+hash,
+                              None, None, ['new'])
+            warnings.warn("'hash' parameter should now be a hashing module")
+
+        self.bytes = numbytes
+        self.bits = self.bytes*8
+        self.entropy = 0
+        self._hash = hash
+
+        # Construct an array to hold the random pool,
+        # initializing it to 0.
+        self._randpool = array.array('B', [0]*self.bytes)
+
+        self._event1 = self._event2 = 0
+        self._addPos = 0
+        self._getPos = hash.digest_size
+        self._lastcounter=time.time()
+        self.__counter = 0
+
+        self._measureTickSize()        # Estimate timer resolution
+        self._randomize()
+
+    def _updateEntropyEstimate(self, nbits):
+        self.entropy += nbits
+        if self.entropy < 0:
+            self.entropy = 0
+        elif self.entropy > self.bits:
+            self.entropy = self.bits
+
+    def _randomize(self, N = 0, devname = '/dev/urandom'):
+        """_randomize(N, DEVNAME:device-filepath)
+        collects N bits of randomness from some entropy source (e.g.,
+        /dev/urandom on Unixes that have it, Windows CryptoAPI
+        CryptGenRandom, etc)
+        DEVNAME is optional, defaults to /dev/urandom.  You can change it
+        to /dev/random if you want to block till you get enough
+        entropy.
+        """
+        data = ''
+        if N <= 0:
+            nbytes = int((self.bits - self.entropy)/8+0.5)
+        else:
+            nbytes = int(N/8+0.5)
+        if winrandom:
+            # Windows CryptGenRandom provides random data.
+            data = winrandom.new().get_bytes(nbytes)
+        elif os.path.exists(devname):
+            # Many OSes support a /dev/urandom device
+            try:
+                f=open(devname)
+                data=f.read(nbytes)
+                f.close()
+            except IOError, (num, msg):
+                if num!=2: raise IOError, (num, msg)
+                # If the file wasn't found, ignore the error
+        if data:
+            self._addBytes(data)
+            # Entropy estimate: The number of bits of
+            # data obtained from the random source.
+            self._updateEntropyEstimate(8*len(data))
+        self.stir_n()                   # Wash the random pool
+
+    def randomize(self, N=0):
+        """randomize(N:int)
+        use the class entropy source to get some entropy data.
+        This is overridden by KeyboardRandomize().
+        """
+        return self._randomize(N)
+
+    def stir_n(self, N = STIRNUM):
+        """stir_n(N)
+        stirs the random pool N times
+        """
+        for i in xrange(N):
+            self.stir()
+
+    def stir (self, s = ''):
+        """stir(s:string)
+        Mix up the randomness pool.  This will call add_event() twice,
+        but out of paranoia the entropy attribute will not be
+        increased.  The optional 's' parameter is a string that will
+        be hashed with the randomness pool.
+        """
+
+        entropy=self.entropy            # Save inital entropy value
+        self.add_event()
+
+        # Loop over the randomness pool: hash its contents
+        # along with a counter, and add the resulting digest
+        # back into the pool.
+        for i in range(self.bytes / self._hash.digest_size):
+            h = self._hash.new(self._randpool)
+            h.update(str(self.__counter) + str(i) + str(self._addPos) + s)
+            self._addBytes( h.digest() )
+            self.__counter = (self.__counter + 1) & 0xFFFFffffL
+
+        self._addPos, self._getPos = 0, self._hash.digest_size
+        self.add_event()
+
+        # Restore the old value of the entropy.
+        self.entropy=entropy
+
+
+    def get_bytes (self, N):
+        """get_bytes(N:int) : string
+        Return N bytes of random data.
+        """
+
+        s=''
+        i, pool = self._getPos, self._randpool
+        h=self._hash.new()
+        dsize = self._hash.digest_size
+        num = N
+        while num > 0:
+            h.update( self._randpool[i:i+dsize] )
+            s = s + h.digest()
+            num = num - dsize
+            i = (i + dsize) % self.bytes
+            if i<dsize:
+                self.stir()
+                i=self._getPos
+
+        self._getPos = i
+        self._updateEntropyEstimate(- 8*N)
+        return s[:N]
+
+
+    def add_event(self, s=''):
+        """add_event(s:string)
+        Add an event to the random pool.  The current time is stored
+        between calls and used to estimate the entropy.  The optional
+        's' parameter is a string that will also be XORed into the pool.
+        Returns the estimated number of additional bits of entropy gain.
+        """
+        event = time.time()*1000
+        delta = self._noise()
+        s = (s + long_to_bytes(event) +
+             4*chr(0xaa) + long_to_bytes(delta) )
+        self._addBytes(s)
+        if event==self._event1 and event==self._event2:
+            # If events are coming too closely together, assume there's
+            # no effective entropy being added.
+            bits=0
+        else:
+            # Count the number of bits in delta, and assume that's the entropy.
+            bits=0
+            while delta:
+                delta, bits = delta>>1, bits+1
+            if bits>8: bits=8
+
+        self._event1, self._event2 = event, self._event1
+
+        self._updateEntropyEstimate(bits)
+        return bits
+
+    # Private functions
+    def _noise(self):
+        # Adds a bit of noise to the random pool, by adding in the
+        # current time and CPU usage of this process.
+        # The difference from the previous call to _noise() is taken
+        # in an effort to estimate the entropy.
+        t=time.time()
+        delta = (t - self._lastcounter)/self._ticksize*1e6
+        self._lastcounter = t
+        self._addBytes(long_to_bytes(long(1000*time.time())))
+        self._addBytes(long_to_bytes(long(1000*time.clock())))
+        self._addBytes(long_to_bytes(long(1000*time.time())))
+        self._addBytes(long_to_bytes(long(delta)))
+
+        # Reduce delta to a maximum of 8 bits so we don't add too much
+        # entropy as a result of this call.
+        delta=delta % 0xff
+        return int(delta)
+
+
+    def _measureTickSize(self):
+        # _measureTickSize() tries to estimate a rough average of the
+        # resolution of time that you can see from Python.  It does
+        # this by measuring the time 100 times, computing the delay
+        # between measurements, and taking the median of the resulting
+        # list.  (We also hash all the times and add them to the pool)
+        interval = [None] * 100
+        h = self._hash.new(`(id(self),id(interval))`)
+
+        # Compute 100 differences
+        t=time.time()
+        h.update(`t`)
+        i = 0
+        j = 0
+        while i < 100:
+            t2=time.time()
+            h.update(`(i,j,t2)`)
+            j += 1
+            delta=int((t2-t)*1e6)
+            if delta:
+                interval[i] = delta
+                i += 1
+                t=t2
+
+        # Take the median of the array of intervals
+        interval.sort()
+        self._ticksize=interval[len(interval)/2]
+        h.update(`(interval,self._ticksize)`)
+        # mix in the measurement times and wash the random pool
+        self.stir(h.digest())
+
+    def _addBytes(self, s):
+        "XOR the contents of the string S into the random pool"
+        i, pool = self._addPos, self._randpool
+        for j in range(0, len(s)):
+            pool[i]=pool[i] ^ ord(s[j])
+            i=(i+1) % self.bytes
+        self._addPos = i
+
+    # Deprecated method names: remove in PCT 2.1 or later.
+    def getBytes(self, N):
+        warnings.warn("getBytes() method replaced by get_bytes()",
+                      DeprecationWarning)
+        return self.get_bytes(N)
+
+    def addEvent (self, event, s=""):
+        warnings.warn("addEvent() method replaced by add_event()",
+                      DeprecationWarning)
+        return self.add_event(s + str(event))
+
+class PersistentRandomPool (RandomPool):
+    def __init__ (self, filename=None, *args, **kwargs):
+        RandomPool.__init__(self, *args, **kwargs)
+        self.filename = filename
+        if filename:
+            try:
+                # the time taken to open and read the file might have
+                # a little disk variability, modulo disk/kernel caching...
+                f=open(filename, 'rb')
+                self.add_event()
+                data = f.read()
+                self.add_event()
+                # mix in the data from the file and wash the random pool
+                self.stir(data)
+                f.close()
+            except IOError:
+                # Oh, well; the file doesn't exist or is unreadable, so
+                # we'll just ignore it.
+                pass
+
+    def save(self):
+        if self.filename == "":
+            raise ValueError, "No filename set for this object"
+        # wash the random pool before save, provides some forward secrecy for
+        # old values of the pool.
+        self.stir_n()
+        f=open(self.filename, 'wb')
+        self.add_event()
+        f.write(self._randpool.tostring())
+        f.close()
+        self.add_event()
+        # wash the pool again, provide some protection for future values
+        self.stir()
+
+# non-echoing Windows keyboard entry
+_kb = 0
+if not _kb:
+    try:
+        import msvcrt
+        class KeyboardEntry:
+            def getch(self):
+                c = msvcrt.getch()
+                if c in ('\000', '\xe0'):
+                    # function key
+                    c += msvcrt.getch()
+                return c
+            def close(self, delay = 0):
+                if delay:
+                    time.sleep(delay)
+                    while msvcrt.kbhit():
+                        msvcrt.getch()
+        _kb = 1
+    except:
+        pass
+
+# non-echoing Posix keyboard entry
+if not _kb:
+    try:
+        import termios
+        class KeyboardEntry:
+            def __init__(self, fd = 0):
+                self._fd = fd
+                self._old = termios.tcgetattr(fd)
+                new = termios.tcgetattr(fd)
+                new[3]=new[3] & ~termios.ICANON & ~termios.ECHO
+                termios.tcsetattr(fd, termios.TCSANOW, new)
+            def getch(self):
+                termios.tcflush(0, termios.TCIFLUSH) # XXX Leave this in?
+                return os.read(self._fd, 1)
+            def close(self, delay = 0):
+                if delay:
+                    time.sleep(delay)
+                    termios.tcflush(self._fd, termios.TCIFLUSH)
+                termios.tcsetattr(self._fd, termios.TCSAFLUSH, self._old)
+        _kb = 1
+    except:
+        pass
+
+class KeyboardRandomPool (PersistentRandomPool):
+    def __init__(self, *args, **kwargs):
+        PersistentRandomPool.__init__(self, *args, **kwargs)
+
+    def randomize(self, N = 0):
+        "Adds N bits of entropy to random pool.  If N is 0, fill up pool."
+        import os, string, time
+        if N <= 0:
+            bits = self.bits - self.entropy
+        else:
+            bits = N*8
+        if bits == 0:
+            return
+        print bits,'bits of entropy are now required.  Please type on the keyboard'
+        print 'until enough randomness has been accumulated.'
+        kb = KeyboardEntry()
+        s=''    # We'll save the characters typed and add them to the pool.
+        hash = self._hash
+        e = 0
+        try:
+            while e < bits:
+                temp=str(bits-e).rjust(6)
+                os.write(1, temp)
+                s=s+kb.getch()
+                e += self.add_event(s)
+                os.write(1, 6*chr(8))
+            self.add_event(s+hash.new(s).digest() )
+        finally:
+            kb.close()
+        print '\n\007 Enough.  Please wait a moment.\n'
+        self.stir_n()   # wash the random pool.
+        kb.close(4)
+
+if __name__ == '__main__':
+    pool = RandomPool()
+    print 'random pool entropy', pool.entropy, 'bits'
+    pool.add_event('something')
+    print `pool.get_bytes(100)`
+    import tempfile, os
+    fname = tempfile.mktemp()
+    pool = KeyboardRandomPool(filename=fname)
+    print 'keyboard random pool entropy', pool.entropy, 'bits'
+    pool.randomize()
+    print 'keyboard random pool entropy', pool.entropy, 'bits'
+    pool.randomize(128)
+    pool.save()
+    saved = open(fname, 'rb').read()
+    print 'saved', `saved`
+    print 'pool ', `pool._randpool.tostring()`
+    newpool = PersistentRandomPool(fname)
+    print 'persistent random pool entropy', pool.entropy, 'bits'
+    os.remove(fname)

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/test.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/Util/test.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,453 @@
+#
+#   test.py : Functions used for testing the modules
+#
+#  Part of the Python Cryptography Toolkit
+#
+# Distribute and use freely; there are no restrictions on further
+# dissemination and usage except those imposed by the laws of your
+# country of residence.  This software is provided "as is" without
+# warranty of fitness for use or suitability for any purpose, express
+# or implied. Use at your own risk or not at all.
+#
+
+__revision__ = "$Id: test.py,v 1.16 2004/08/13 22:24:18 akuchling Exp $"
+
+import binascii
+import string
+import testdata
+
+from Crypto.Cipher import *
+
+def die(string):
+    import sys
+    print '***ERROR: ', string
+#    sys.exit(0)   # Will default to continuing onward...
+
+def print_timing (size, delta, verbose):
+    if verbose:
+        if delta == 0:
+            print 'Unable to measure time -- elapsed time too small'
+        else:
+            print '%.2f K/sec' % (size/delta)
+            
+def exerciseBlockCipher(cipher, verbose):
+    import string, time
+    try:
+        ciph = eval(cipher)
+    except NameError:
+        print cipher, 'module not available'
+        return None
+    print cipher+ ':'
+    str='1'                             # Build 128K of test data
+    for i in xrange(0, 17):
+        str=str+str
+    if ciph.key_size==0: ciph.key_size=16
+    password = 'password12345678Extra text for password'[0:ciph.key_size]
+    IV = 'Test IV Test IV Test IV Test'[0:ciph.block_size]
+
+    if verbose: print '  ECB mode:',
+    obj=ciph.new(password, ciph.MODE_ECB)
+    if obj.block_size != ciph.block_size:
+        die("Module and cipher object block_size don't match")
+
+    text='1234567812345678'[0:ciph.block_size]
+    c=obj.encrypt(text)
+    if (obj.decrypt(c)!=text): die('Error encrypting "'+text+'"')
+    text='KuchlingKuchling'[0:ciph.block_size]
+    c=obj.encrypt(text)
+    if (obj.decrypt(c)!=text): die('Error encrypting "'+text+'"')
+    text='NotTodayNotEver!'[0:ciph.block_size]
+    c=obj.encrypt(text)
+    if (obj.decrypt(c)!=text): die('Error encrypting "'+text+'"')
+
+    start=time.time()
+    s=obj.encrypt(str)
+    s2=obj.decrypt(s)
+    end=time.time()
+    if (str!=s2):
+        die('Error in resulting plaintext from ECB mode')
+    print_timing(256, end-start, verbose)
+    del obj
+
+    if verbose: print '  CFB mode:',
+    obj1=ciph.new(password, ciph.MODE_CFB, IV)
+    obj2=ciph.new(password, ciph.MODE_CFB, IV)
+    start=time.time()
+    ciphertext=obj1.encrypt(str[0:65536])
+    plaintext=obj2.decrypt(ciphertext)
+    end=time.time()
+    if (plaintext!=str[0:65536]):
+        die('Error in resulting plaintext from CFB mode')
+    print_timing(64, end-start, verbose)
+    del obj1, obj2
+
+    if verbose: print '  CBC mode:',
+    obj1=ciph.new(password, ciph.MODE_CBC, IV)
+    obj2=ciph.new(password, ciph.MODE_CBC, IV)
+    start=time.time()
+    ciphertext=obj1.encrypt(str)
+    plaintext=obj2.decrypt(ciphertext)
+    end=time.time()
+    if (plaintext!=str):
+        die('Error in resulting plaintext from CBC mode')
+    print_timing(256, end-start, verbose)
+    del obj1, obj2
+
+    if verbose: print '  PGP mode:',
+    obj1=ciph.new(password, ciph.MODE_PGP, IV)
+    obj2=ciph.new(password, ciph.MODE_PGP, IV)
+    start=time.time()
+    ciphertext=obj1.encrypt(str)
+    plaintext=obj2.decrypt(ciphertext)
+    end=time.time()
+    if (plaintext!=str):
+        die('Error in resulting plaintext from PGP mode')
+    print_timing(256, end-start, verbose)
+    del obj1, obj2
+
+    if verbose: print '  OFB mode:',
+    obj1=ciph.new(password, ciph.MODE_OFB, IV)
+    obj2=ciph.new(password, ciph.MODE_OFB, IV)
+    start=time.time()
+    ciphertext=obj1.encrypt(str)
+    plaintext=obj2.decrypt(ciphertext)
+    end=time.time()
+    if (plaintext!=str):
+        die('Error in resulting plaintext from OFB mode')
+    print_timing(256, end-start, verbose)
+    del obj1, obj2
+
+    def counter(length=ciph.block_size):
+        return length * 'a'
+
+    if verbose: print '  CTR mode:',
+    obj1=ciph.new(password, ciph.MODE_CTR, counter=counter)
+    obj2=ciph.new(password, ciph.MODE_CTR, counter=counter)
+    start=time.time()
+    ciphertext=obj1.encrypt(str)
+    plaintext=obj2.decrypt(ciphertext)
+    end=time.time()
+    if (plaintext!=str):
+        die('Error in resulting plaintext from CTR mode')
+    print_timing(256, end-start, verbose)
+    del obj1, obj2
+
+    # Test the IV handling
+    if verbose: print '  Testing IV handling'
+    obj1=ciph.new(password, ciph.MODE_CBC, IV)
+    plaintext='Test'*(ciph.block_size/4)*3
+    ciphertext1=obj1.encrypt(plaintext)
+    obj1.IV=IV
+    ciphertext2=obj1.encrypt(plaintext)
+    if ciphertext1!=ciphertext2:
+        die('Error in setting IV')
+
+    # Test keyword arguments
+    obj1=ciph.new(key=password)
+    obj1=ciph.new(password, mode=ciph.MODE_CBC)
+    obj1=ciph.new(mode=ciph.MODE_CBC, key=password)
+    obj1=ciph.new(IV=IV, mode=ciph.MODE_CBC, key=password)
+
+    return ciph
+
+def exerciseStreamCipher(cipher, verbose):
+    import string, time
+    try:
+        ciph = eval(cipher)
+    except (NameError):
+        print cipher, 'module not available'
+        return None
+    print cipher + ':',
+    str='1'                             # Build 128K of test data
+    for i in xrange(0, 17):
+        str=str+str
+    key_size = ciph.key_size or 16
+    password = 'password12345678Extra text for password'[0:key_size]
+
+    obj1=ciph.new(password)
+    obj2=ciph.new(password)
+    if obj1.block_size != ciph.block_size:
+        die("Module and cipher object block_size don't match")
+    if obj1.key_size != ciph.key_size:
+        die("Module and cipher object key_size don't match")
+
+    text='1234567812345678Python'
+    c=obj1.encrypt(text)
+    if (obj2.decrypt(c)!=text): die('Error encrypting "'+text+'"')
+    text='B1FF I2 A R3A11Y |<00L D00D!!!!!'
+    c=obj1.encrypt(text)
+    if (obj2.decrypt(c)!=text): die('Error encrypting "'+text+'"')
+    text='SpamSpamSpamSpamSpamSpamSpamSpamSpam'
+    c=obj1.encrypt(text)
+    if (obj2.decrypt(c)!=text): die('Error encrypting "'+text+'"')
+
+    start=time.time()
+    s=obj1.encrypt(str)
+    str=obj2.decrypt(s)
+    end=time.time()
+    print_timing(256, end-start, verbose)
+    del obj1, obj2
+
+    return ciph
+
+def TestStreamModules(args=['arc4', 'XOR'], verbose=1):
+    import sys, string
+    args=map(string.lower, args)
+
+    if 'arc4' in args:
+        # Test ARC4 stream cipher
+        arc4=exerciseStreamCipher('ARC4', verbose)
+        if (arc4!=None):
+                for entry in testdata.arc4:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=arc4.new(key)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('ARC4 failed on entry '+`entry`)
+
+    if 'xor' in args:
+        # Test XOR stream cipher
+        XOR=exerciseStreamCipher('XOR', verbose)
+        if (XOR!=None):
+                for entry in testdata.xor:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=XOR.new(key)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('XOR failed on entry '+`entry`)
+
+
+def TestBlockModules(args=['aes', 'arc2', 'des', 'blowfish', 'cast', 'des3',
+                           'idea', 'rc5'],
+                     verbose=1):
+    import string
+    args=map(string.lower, args)
+    if 'aes' in args:
+        ciph=exerciseBlockCipher('AES', verbose)        # AES
+        if (ciph!=None):
+                if verbose: print '  Verifying against test suite...'
+                for entry in testdata.aes:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key, ciph.MODE_ECB)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('AES failed on entry '+`entry`)
+                        for i in ciphertext:
+                            if verbose: print hex(ord(i)),
+                        if verbose: print
+
+                for entry in testdata.aes_modes:
+                    mode, key, plain, cipher, kw = entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key, mode, **kw)
+                    obj2=ciph.new(key, mode, **kw)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('AES encrypt failed on entry '+`entry`)
+                        for i in ciphertext:
+                            if verbose: print hex(ord(i)),
+                        if verbose: print
+
+                    plain2=obj2.decrypt(ciphertext)
+                    if plain2!=plain:
+                        die('AES decrypt failed on entry '+`entry`)
+                        for i in plain2:
+                            if verbose: print hex(ord(i)),
+                        if verbose: print
+
+
+    if 'arc2' in args:
+        ciph=exerciseBlockCipher('ARC2', verbose)           # Alleged RC2
+        if (ciph!=None):
+                if verbose: print '  Verifying against test suite...'
+                for entry in testdata.arc2:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key, ciph.MODE_ECB)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('ARC2 failed on entry '+`entry`)
+                        for i in ciphertext:
+                            if verbose: print hex(ord(i)),
+                        print
+
+    if 'blowfish' in args:
+        ciph=exerciseBlockCipher('Blowfish',verbose)# Bruce Schneier's Blowfish cipher
+        if (ciph!=None):
+                if verbose: print '  Verifying against test suite...'
+                for entry in testdata.blowfish:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key, ciph.MODE_ECB)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('Blowfish failed on entry '+`entry`)
+                        for i in ciphertext:
+                            if verbose: print hex(ord(i)),
+                        if verbose: print
+
+    if 'cast' in args:
+        ciph=exerciseBlockCipher('CAST', verbose)        # CAST-128
+        if (ciph!=None):
+                if verbose: print '  Verifying against test suite...'
+                for entry in testdata.cast:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key, ciph.MODE_ECB)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('CAST failed on entry '+`entry`)
+                        for i in ciphertext:
+                            if verbose: print hex(ord(i)),
+                        if verbose: print
+
+                if 0:
+                    # The full-maintenance test; it requires 4 million encryptions,
+                    # and correspondingly is quite time-consuming.  I've disabled
+                    # it; it's faster to compile block/cast.c with -DTEST and run
+                    # the resulting program.
+                    a = b = '\x01\x23\x45\x67\x12\x34\x56\x78\x23\x45\x67\x89\x34\x56\x78\x9A'
+
+                    for i in range(0, 1000000):
+                        obj = cast.new(b, cast.MODE_ECB)
+                        a = obj.encrypt(a[:8]) + obj.encrypt(a[-8:])
+                        obj = cast.new(a, cast.MODE_ECB)
+                        b = obj.encrypt(b[:8]) + obj.encrypt(b[-8:])
+
+                    if a!="\xEE\xA9\xD0\xA2\x49\xFD\x3B\xA6\xB3\x43\x6F\xB8\x9D\x6D\xCA\x92":
+                        if verbose: print 'CAST test failed: value of "a" doesn\'t match'
+                    if b!="\xB2\xC9\x5E\xB0\x0C\x31\xAD\x71\x80\xAC\x05\xB8\xE8\x3D\x69\x6E":
+                        if verbose: print 'CAST test failed: value of "b" doesn\'t match'
+
+    if 'des' in args:
+        # Test/benchmark DES block cipher
+        des=exerciseBlockCipher('DES', verbose)
+        if (des!=None):
+            # Various tests taken from the DES library packaged with Kerberos V4
+            obj=des.new(binascii.a2b_hex('0123456789abcdef'), des.MODE_ECB)
+            s=obj.encrypt('Now is t')
+            if (s!=binascii.a2b_hex('3fa40e8a984d4815')):
+                die('DES fails test 1')
+            obj=des.new(binascii.a2b_hex('08192a3b4c5d6e7f'), des.MODE_ECB)
+            s=obj.encrypt('\000\000\000\000\000\000\000\000')
+            if (s!=binascii.a2b_hex('25ddac3e96176467')):
+                die('DES fails test 2')
+            obj=des.new(binascii.a2b_hex('0123456789abcdef'), des.MODE_CBC,
+                        binascii.a2b_hex('1234567890abcdef'))
+            s=obj.encrypt("Now is the time for all ")
+            if (s!=binascii.a2b_hex('e5c7cdde872bf27c43e934008c389c0f683788499a7c05f6')):
+                die('DES fails test 3')
+            obj=des.new(binascii.a2b_hex('0123456789abcdef'), des.MODE_CBC,
+                        binascii.a2b_hex('fedcba9876543210'))
+            s=obj.encrypt("7654321 Now is the time for \000\000\000\000")
+            if (s!=binascii.a2b_hex("ccd173ffab2039f4acd8aefddfd8a1eb468e91157888ba681d269397f7fe62b4")):
+                die('DES fails test 4')
+            del obj,s
+
+            # R. Rivest's test: see http://theory.lcs.mit.edu/~rivest/destest.txt
+            x=binascii.a2b_hex('9474B8E8C73BCA7D')
+            for i in range(0, 16):
+                obj=des.new(x, des.MODE_ECB)
+                if (i & 1): x=obj.decrypt(x)
+                else: x=obj.encrypt(x)
+            if x!=binascii.a2b_hex('1B1A2DDB4C642438'):
+                die("DES fails Rivest's test")
+
+            if verbose: print '  Verifying against test suite...'
+            for entry in testdata.des:
+                key,plain,cipher=entry
+                key=binascii.a2b_hex(key)
+                plain=binascii.a2b_hex(plain)
+                cipher=binascii.a2b_hex(cipher)
+                obj=des.new(key, des.MODE_ECB)
+                ciphertext=obj.encrypt(plain)
+                if (ciphertext!=cipher):
+                    die('DES failed on entry '+`entry`)
+            for entry in testdata.des_cbc:
+                key, iv, plain, cipher=entry
+                key, iv, cipher=binascii.a2b_hex(key),binascii.a2b_hex(iv),binascii.a2b_hex(cipher)
+                obj1=des.new(key, des.MODE_CBC, iv)
+                obj2=des.new(key, des.MODE_CBC, iv)
+                ciphertext=obj1.encrypt(plain)
+                if (ciphertext!=cipher):
+                    die('DES CBC mode failed on entry '+`entry`)
+
+    if 'des3' in args:
+        ciph=exerciseBlockCipher('DES3', verbose)        # Triple DES
+        if (ciph!=None):
+                if verbose: print '  Verifying against test suite...'
+                for entry in testdata.des3:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key, ciph.MODE_ECB)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('DES3 failed on entry '+`entry`)
+                        for i in ciphertext:
+                            if verbose: print hex(ord(i)),
+                        if verbose: print
+                for entry in testdata.des3_cbc:
+                    key, iv, plain, cipher=entry
+                    key, iv, cipher=binascii.a2b_hex(key),binascii.a2b_hex(iv),binascii.a2b_hex(cipher)
+                    obj1=ciph.new(key, ciph.MODE_CBC, iv)
+                    obj2=ciph.new(key, ciph.MODE_CBC, iv)
+                    ciphertext=obj1.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('DES3 CBC mode failed on entry '+`entry`)
+
+    if 'idea' in args:
+        ciph=exerciseBlockCipher('IDEA', verbose)       # IDEA block cipher
+        if (ciph!=None):
+                if verbose: print '  Verifying against test suite...'
+                for entry in testdata.idea:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key, ciph.MODE_ECB)
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('IDEA failed on entry '+`entry`)
+
+    if 'rc5' in args:
+        # Ronald Rivest's RC5 algorithm
+        ciph=exerciseBlockCipher('RC5', verbose)
+        if (ciph!=None):
+                if verbose: print '  Verifying against test suite...'
+                for entry in testdata.rc5:
+                    key,plain,cipher=entry
+                    key=binascii.a2b_hex(key)
+                    plain=binascii.a2b_hex(plain)
+                    cipher=binascii.a2b_hex(cipher)
+                    obj=ciph.new(key[4:], ciph.MODE_ECB,
+                                 version =ord(key[0]),
+                                 word_size=ord(key[1]),
+                                 rounds  =ord(key[2]) )
+                    ciphertext=obj.encrypt(plain)
+                    if (ciphertext!=cipher):
+                        die('RC5 failed on entry '+`entry`)
+                        for i in ciphertext:
+                            if verbose: print hex(ord(i)),
+                        if verbose: print
+
+
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,25 @@
+
+"""Python Cryptography Toolkit
+
+A collection of cryptographic modules implementing various algorithms
+and protocols.
+
+Subpackages:
+Crypto.Cipher             Secret-key encryption algorithms (AES, DES, ARC4)
+Crypto.Hash               Hashing algorithms (MD5, SHA, HMAC)
+Crypto.Protocol           Cryptographic protocols (Chaffing, all-or-nothing
+                          transform).   This package does not contain any
+                          network protocols.
+Crypto.PublicKey          Public-key encryption and signature algorithms
+                          (RSA, DSA)
+Crypto.Util               Various useful modules and functions (long-to-string
+                          conversion, random number generation, number
+                          theoretic functions)
+"""
+
+__all__ = ['Cipher', 'Hash', 'Protocol', 'PublicKey', 'Util']
+
+__version__ = '2.0.1'
+__revision__ = "$Id: __init__.py,v 1.12 2005/06/14 01:20:22 akuchling Exp $"
+
+

Added: trunk/conduit/modules/GoogleModule/gdata/Crypto/test.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/Crypto/test.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,38 @@
+#
+# Test script for the Python Cryptography Toolkit.
+#
+
+__revision__ = "$Id: test.py,v 1.7 2002/07/11 14:31:19 akuchling Exp $"
+
+import os, sys
+
+
+# Add the build directory to the front of sys.path
+from distutils.util import get_platform
+s = "build/lib.%s-%.3s" % (get_platform(), sys.version)
+s = os.path.join(os.getcwd(), s)
+sys.path.insert(0, s)
+s = os.path.join(os.getcwd(), 'test')
+sys.path.insert(0, s)
+
+from Crypto.Util import test
+
+args = sys.argv[1:]
+quiet = "--quiet" in args
+if quiet: args.remove('--quiet')
+
+if not quiet:
+    print '\nStream Ciphers:'
+    print '==============='
+
+if args: test.TestStreamModules(args, verbose= not quiet)
+else: test.TestStreamModules(verbose= not quiet)
+
+if not quiet:
+    print '\nBlock Ciphers:'
+    print '=============='
+
+if args: test.TestBlockModules(args, verbose= not quiet)
+else: test.TestBlockModules(verbose= not quiet)
+
+

Modified: trunk/conduit/modules/GoogleModule/gdata/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/__init__.py	Tue Mar 17 09:00:35 2009
@@ -25,6 +25,16 @@
 
 import os
 import atom
+try:
+  from xml.etree import cElementTree as ElementTree
+except ImportError:
+  try:
+    import cElementTree as ElementTree
+  except ImportError:
+    try:
+      from xml.etree import ElementTree
+    except ImportError:
+      from elementtree import ElementTree
 
 
 # XML namespaces which are often used in GData entities.
@@ -79,8 +89,7 @@
 
     if (file_handle is None and content_type is not None and
         file_path is not None):
-
-        self.setFile(file_path, content_type)
+      self.setFile(file_path, content_type)
 
   def setFile(self, file_name, content_type):
     """A helper function which can create a file handle from a given filename
@@ -179,6 +188,12 @@
         return a_link
     return None
 
+  def GetPrevLink(self):
+    for a_link in self.link:
+      if a_link.rel == 'previous':
+        return a_link
+    return None
+
 
 class TotalResults(atom.AtomBase):
   """opensearch:TotalResults for a GData feed"""
@@ -237,6 +252,82 @@
   return atom.CreateClassFromXMLString(ItemsPerPage, xml_string)
 
 
+class ExtendedProperty(atom.AtomBase):
+  """The Google Data extendedProperty element.
+  
+  Used to store arbitrary key-value information specific to your
+  application. The value can either be a text string stored as an XML 
+  attribute (.value), or an XML node (XmlBlob) as a child element.
+
+  This element is used in the Google Calendar data API and the Google
+  Contacts data API.
+  """
+
+  _tag = 'extendedProperty'
+  _namespace = GDATA_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _attributes['name'] = 'name'
+  _attributes['value'] = 'value'
+
+  def __init__(self, name=None, value=None, extension_elements=None,
+      extension_attributes=None, text=None):
+    self.name = name
+    self.value = value
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+  def GetXmlBlobExtensionElement(self):
+    """Returns the XML blob as an atom.ExtensionElement.
+    
+    Returns:
+      An atom.ExtensionElement representing the blob's XML, or None if no
+      blob was set.
+    """
+    if len(self.extension_elements) < 1:
+      return None
+    else:
+      return self.extension_elements[0]
+
+  def GetXmlBlobString(self):
+    """Returns the XML blob as a string.
+
+    Returns:
+      A string containing the blob's XML, or None if no blob was set.
+    """
+    blob = self.GetXmlBlobExtensionElement()
+    if blob:
+      return blob.ToString()
+    return None
+
+  def SetXmlBlob(self, blob):
+    """Sets the contents of the extendedProperty to XML as a child node.
+
+    Since the extendedProperty is only allowed one child element as an XML
+    blob, setting the XML blob will erase any preexisting extension elements
+    in this object.
+
+    Args:
+      blob: str, ElementTree Element or atom.ExtensionElement representing
+            the XML blob stored in the extendedProperty.
+    """
+    # Erase any existing extension_elements, clears the child nodes from the
+    # extendedProperty.
+    self.extension_elements = []
+    if isinstance(blob, atom.ExtensionElement):
+      self.extension_elements.append(blob)
+    elif ElementTree.iselement(blob):
+      self.extension_elements.append(atom._ExtensionElementFromElementTree(
+          blob))
+    else:
+      self.extension_elements.append(atom.ExtensionElementFromString(blob))
+
+
+def ExtendedPropertyFromString(xml_string):
+  return atom.CreateClassFromXMLString(ExtendedProperty, xml_string)
+
+
 class GDataEntry(atom.Entry, LinkFinder):
   """Extends Atom Entry to provide data processing"""
 
@@ -694,7 +785,7 @@
   _attributes = atom.AtomBase._attributes.copy()
   # The entry used to be an atom.Entry, now it is a GDataEntry.
   _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', GDataEntry)
-  _attributes['rel'] = 'rel',
+  _attributes['rel'] = 'rel'
   _attributes['readOnly'] = 'read_only'
   _attributes['href'] = 'href'
   

Added: trunk/conduit/modules/GoogleModule/gdata/alt/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/alt/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,20 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""This package's modules adapt the gdata library to run in other environments
+
+The first example is the appengine module which contains functions and
+classes which modify a GDataService object to run on Google App Engine.
+"""

Added: trunk/conduit/modules/GoogleModule/gdata/alt/appengine.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/alt/appengine.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Provides HTTP functions for gdata.service to use on Google App Engine
+
+AppEngineHttpClient: Provides an HTTP request method which uses App Engine's
+   urlfetch API. Set the http_client member of a GDataService object to an
+   instance of an AppEngineHttpClient to allow the gdata library to run on
+   Google App Engine.
+
+run_on_appengine: Function which will modify an existing GDataService object
+   to allow it to run on App Engine. It works by creating a new instance of
+   the AppEngineHttpClient and replacing the GDataService object's
+   http_client.
+"""
+
+
+__author__ = 'api.jscudder (Jeff Scudder)'
+
+
+import StringIO
+import pickle
+import atom.http_interface
+import atom.token_store
+from google.appengine.api import urlfetch
+from google.appengine.ext import db
+from google.appengine.api import users
+from google.appengine.api import memcache
+
+
+def run_on_appengine(gdata_service, store_tokens=True, 
+    single_user_mode=False):
+  """Modifies a GDataService object to allow it to run on App Engine.
+
+  Args:
+    gdata_service: An instance of AtomService, GDataService, or any
+        of their subclasses which has an http_client member and a 
+        token_store member.
+    store_tokens: Boolean, defaults to True. If True, the gdata_service
+                  will attempt to add each token to it's token_store when
+                  SetClientLoginToken or SetAuthSubToken is called. If False
+                  the tokens will not automatically be added to the 
+                  token_store.
+    single_user_mode: Boolean, defaults to False. If True, the current_token
+                      member of gdata_service will be set when 
+                      SetClientLoginToken or SetAuthTubToken is called. If set
+                      to True, the current_token is set in the gdata_service
+                      and anyone who accesses the object will use the same 
+                      token. 
+                      
+                      Note: If store_tokens is set to False and 
+                      single_user_mode is set to False, all tokens will be 
+                      ignored, since the library assumes: the tokens should not
+                      be stored in the datastore and they should not be stored
+                      in the gdata_service object. This will make it 
+                      impossible to make requests which require authorization.
+  """
+  gdata_service.http_client = AppEngineHttpClient()
+  gdata_service.token_store = AppEngineTokenStore()
+  gdata_service.auto_store_tokens = store_tokens
+  gdata_service.auto_set_current_token = single_user_mode
+  return gdata_service
+
+
+class AppEngineHttpClient(atom.http_interface.GenericHttpClient):
+  def __init__(self, headers=None):
+    self.debug = False
+    self.headers = headers or {}
+
+  def request(self, operation, url, data=None, headers=None):
+    """Performs an HTTP call to the server, supports GET, POST, PUT, and
+    DELETE.
+
+    Usage example, perform and HTTP GET on http://www.google.com/:
+      import atom.http
+      client = atom.http.HttpClient()
+      http_response = client.request('GET', 'http://www.google.com/')
+
+    Args:
+      operation: str The HTTP operation to be performed. This is usually one
+          of 'GET', 'POST', 'PUT', or 'DELETE'
+      data: filestream, list of parts, or other object which can be converted
+          to a string. Should be set to None when performing a GET or DELETE.
+          If data is a file-like object which can be read, this method will
+          read a chunk of 100K bytes at a time and send them.
+          If the data is a list of parts to be sent, each part will be
+          evaluated and sent.
+      url: The full URL to which the request should be sent. Can be a string
+          or atom.url.Url.
+      headers: dict of strings. HTTP headers which should be sent
+          in the request.
+    """
+    all_headers = self.headers.copy()
+    if headers:
+      all_headers.update(headers)
+
+    # Construct the full payload.
+    # Assume that data is None or a string.
+    data_str = data
+    if data:
+      if isinstance(data, list):
+        # If data is a list of different objects, convert them all to strings
+        # and join them together.
+        converted_parts = [_convert_data_part(x) for x in data]
+        data_str = ''.join(converted_parts)
+      else:
+        data_str = _convert_data_part(data)
+
+    # If the list of headers does not include a Content-Length, attempt to
+    # calculate it based on the data object.
+    if data and 'Content-Length' not in all_headers:
+      all_headers['Content-Length'] = str(len(data_str))
+
+    # Set the content type to the default value if none was set.
+    if 'Content-Type' not in all_headers:
+      all_headers['Content-Type'] = 'application/atom+xml'
+
+    # Lookup the urlfetch operation which corresponds to the desired HTTP verb.
+    if operation == 'GET':
+      method = urlfetch.GET
+    elif operation == 'POST':
+      method = urlfetch.POST
+    elif operation == 'PUT':
+      method = urlfetch.PUT
+    elif operation == 'DELETE':
+      method = urlfetch.DELETE
+    else:
+      method = None
+    return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
+        method=method, headers=all_headers, follow_redirects=False))
+
+
+def _convert_data_part(data):
+  if not data or isinstance(data, str):
+    return data
+  elif hasattr(data, 'read'):
+    # data is a file like object, so read it completely.
+    return data.read()
+  # The data object was not a file.
+  # Try to convert to a string and send the data.
+  return str(data)
+
+
+class HttpResponse(object):
+  """Translates a urlfetch resoinse to look like an hhtplib resoinse.
+
+  Used to allow the resoinse from HttpRequest to be usable by gdata.service
+  methods.
+  """
+
+  def __init__(self, urlfetch_response):
+    self.body = StringIO.StringIO(urlfetch_response.content)
+    self.headers = urlfetch_response.headers
+    self.status = urlfetch_response.status_code
+    self.reason = ''
+
+  def read(self, length=None):
+    if not length:
+      return self.body.read()
+    else:
+      return self.body.read(length)
+
+  def getheader(self, name):
+    if not self.headers.has_key(name):
+      return self.headers[name.lower()]
+    return self.headers[name]
+
+
+class TokenCollection(db.Model):
+  """Datastore Model which associates auth tokens with the current user."""
+  user = db.UserProperty()
+  pickled_tokens = db.BlobProperty()
+
+
+class AppEngineTokenStore(atom.token_store.TokenStore):
+  """Stores the user's auth tokens in the App Engine datastore.
+
+  Tokens are only written to the datastore if a user is signed in (if 
+  users.get_current_user() returns a user object).
+  """
+  def __init__(self):
+    pass
+
+  def add_token(self, token):
+    """Associates the token with the current user and stores it.
+    
+    If there is no current user, the token will not be stored.
+
+    Returns:
+      False if the token was not stored. 
+    """
+    tokens = load_auth_tokens()
+    if not hasattr(token, 'scopes') or not token.scopes:
+      return False
+    for scope in token.scopes:
+      tokens[str(scope)] = token
+    key = save_auth_tokens(tokens)
+    if key:
+      return True
+    return False
+
+  def find_token(self, url):
+    """Searches the current user's collection of token for a token which can
+    be used for a request to the url.
+
+    Returns:
+      The stored token which belongs to the current user and is valid for the
+      desired URL. If there is no current user, or there is no valid user 
+      token in the datastore, a atom.http_interface.GenericToken is returned.
+    """
+    if url is None:
+      return None
+    if isinstance(url, (str, unicode)):
+      url = atom.url.parse_url(url)
+    tokens = load_auth_tokens()
+    if url in tokens:
+      token = tokens[url]
+      if token.valid_for_scope(url):
+        return token
+      else:
+        del tokens[url]
+        save_auth_tokens(tokens)
+    for scope, token in tokens.iteritems():
+      if token.valid_for_scope(url):
+        return token
+    return atom.http_interface.GenericToken()
+
+  def remove_token(self, token):
+    """Removes the token from the current user's collection in the datastore.
+    
+    Returns:
+      False if the token was not removed, this could be because the token was
+      not in the datastore, or because there is no current user.
+    """
+    token_found = False
+    scopes_to_delete = []
+    tokens = load_auth_tokens()
+    for scope, stored_token in tokens.iteritems():
+      if stored_token == token:
+        scopes_to_delete.append(scope)
+        token_found = True
+    for scope in scopes_to_delete:
+      del tokens[scope]
+    if token_found:
+      save_auth_tokens(tokens)
+    return token_found
+
+  def remove_all_tokens(self):
+    """Removes all of the current user's tokens from the datastore."""
+    save_auth_tokens({})
+
+
+def save_auth_tokens(token_dict):
+  """Associates the tokens with the current user and writes to the datastore.
+  
+  If there us no current user, the tokens are not written and this function
+  returns None.
+
+  Returns:
+    The key of the datastore entity containing the user's tokens, or None if
+    there was no current user.
+  """
+  if users.get_current_user() is None:
+    return None
+  user_tokens = TokenCollection.all().filter('user =', users.get_current_user()).get()
+  if user_tokens:
+    user_tokens.pickled_tokens = pickle.dumps(token_dict)
+    return user_tokens.put()
+  else:
+    user_tokens = TokenCollection(
+        user=users.get_current_user(), 
+        pickled_tokens=pickle.dumps(token_dict))
+    return user_tokens.put()
+     
+
+def load_auth_tokens():
+  """Reads a dictionary of the current user's tokens from the datastore.
+  
+  If there is no current user (a user is not signed in to the app) or the user
+  does not have any tokens, an empty dictionary is returned.
+  """
+  if users.get_current_user() is None:
+    return {}
+  user_tokens = TokenCollection.all().filter('user =', users.get_current_user()).get()
+  if user_tokens:
+    return pickle.loads(user_tokens.pickled_tokens)
+  return {}
+

Modified: trunk/conduit/modules/GoogleModule/gdata/apps/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/apps/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/apps/__init__.py	Tue Mar 17 09:00:35 2009
@@ -442,10 +442,55 @@
   return atom.CreateClassFromXMLString(EmailListRecipientFeed, xml_string)
 
 
+class Property(atom.AtomBase):
+  """The Google Apps Property element"""
 
+  _tag = 'property'
+  _namespace = APPS_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _attributes['name'] = 'name'
+  _attributes['value'] = 'value'
+
+  def __init__(self, name=None, value=None, extension_elements=None,
+               extension_attributes=None, text=None):
+    self.name = name
+    self.value = value
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def PropertyFromString(xml_string):
+  return atom.CreateClassFromXMLString(Property, xml_string)
+
+
+class PropertyEntry(gdata.GDataEntry):
+  """A Google Apps Property flavor of an Atom Entry"""
+
+  _tag = 'entry'
+  _namespace = atom.ATOM_NAMESPACE
+  _children = gdata.GDataEntry._children.copy()
+  _attributes = gdata.GDataEntry._attributes.copy()
+  _children['{%s}property' % APPS_NAMESPACE] = ('property', [Property])
+
+  def __init__(self, author=None, category=None, content=None,
+               atom_id=None, link=None, published=None,
+               title=None, updated=None,
+               property=None,
+               extended_property=None,
+               extension_elements=None, extension_attributes=None, text=None):
+
+    gdata.GDataEntry.__init__(self, author=author, category=category,
+                              content=content,
+                              atom_id=atom_id, link=link, published=published,
+                              title=title, updated=updated)
+    self.property = property
+    self.extended_property = extended_property or []
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
 
 
-
-
-
-
+def PropertyEntryFromString(xml_string):
+  return atom.CreateClassFromXMLString(PropertyEntry, xml_string)

Added: trunk/conduit/modules/GoogleModule/gdata/apps/emailsettings/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/apps/emailsettings/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,15 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.

Added: trunk/conduit/modules/GoogleModule/gdata/apps/emailsettings/service.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/apps/emailsettings/service.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,250 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Allow Google Apps domain administrators to set users' email settings.
+
+  EmailSettingsService: Set various email settings.
+"""
+
+__author__ = 'google-apps-apis googlegroups com'
+
+
+import gdata.apps
+import gdata.apps.service
+import gdata.service
+
+
+API_VER='2.0'
+# Forwarding and POP3 options
+KEEP='KEEP'
+ARCHIVE='ARCHIVE'
+DELETE='DELETE'
+ALL_MAIL='ALL_MAIL'
+MAIL_FROM_NOW_ON='MAIL_FROM_NOW_ON'
+
+
+class EmailSettingsService(gdata.apps.service.PropertyService):
+  """Client for the Google Apps Email Settings service."""
+
+  def _serviceUrl(self, setting_id, username, domain=None):
+    if domain is None:
+      domain = self.domain
+    return '/a/feeds/emailsettings/%s/%s/%s/%s' % (API_VER, domain, username,
+                                                   setting_id)
+
+  def _bool2str(self, b):
+    if b is None:
+      return None
+    return str(b is True).lower()
+
+  def CreateLabel(self, username, label):
+    """Create a label.
+
+    Args:
+      username: User to create label for.
+      label: Label to create.
+
+    Returns:
+      A dict containing the result of the create operation.
+    """
+    uri = self._serviceUrl('label', username)
+    properties = {'label': label}
+    return self._PostProperties(uri, properties)
+
+  def CreateFilter(self, username, from_=None, to=None, subject=None,
+                   has_the_word=None, does_not_have_the_word=None,
+                   has_attachment=None, label=None, should_mark_as_read=None,
+                   should_archive=None):
+    """Create a filter.
+
+    Args:
+      username: User to create filter for.
+      from_: Filter from string.
+      to: Filter to string.
+      subject: Filter subject.
+      has_the_word: Words to filter in.
+      does_not_have_the_word: Words to filter out.
+      has_attachment:  Boolean for message having attachment.
+      label: Label to apply.
+      should_mark_as_read: Boolean for marking message as read.
+      should_archive: Boolean for archiving message.
+
+    Returns:
+      A dict containing the result of the create operation.
+    """
+    uri = self._serviceUrl('filter', username)
+    properties = {}
+    properties['from'] = from_
+    properties['to'] = to
+    properties['subject'] = subject
+    properties['hasTheWord'] = has_the_word
+    properties['doesNotHaveTheWord'] = does_not_have_the_word
+    properties['hasAttachment'] = self._bool2str(has_attachment)
+    properties['label'] = label
+    properties['shouldMarkAsRead'] = self._bool2str(should_mark_as_read)
+    properties['shouldArchive'] = self._bool2str(should_archive)
+    return self._PostProperties(uri, properties)
+
+  def CreateSendAsAlias(self, username, name, address, reply_to=None,
+                        make_default=None):
+    """Create alias to send mail as.
+
+    Args:
+      username: User to create alias for.
+      name: Name of alias.
+      address: Email address to send from.
+      reply_to: Email address to reply to.
+      make_default: Boolean for whether this is the new default sending alias.
+
+    Returns:
+      A dict containing the result of the create operation.
+    """
+    uri = self._serviceUrl('sendas', username)
+    properties = {}
+    properties['name'] = name
+    properties['address'] = address
+    properties['replyTo'] = reply_to
+    properties['makeDefault'] = self._bool2str(make_default)
+    return self._PostProperties(uri, properties)
+
+  def UpdateForwarding(self, username, enable, forward_to=None, action=None):
+    """Update forwarding settings.
+
+    Args:
+      username: User to update forwarding for.
+      enable: Boolean whether to enable this forwarding rule.
+      forward_to: Email address to forward to.
+      action: Action to take after forwarding.
+
+    Returns:
+      A dict containing the result of the update operation.
+    """
+    uri = self._serviceUrl('forwarding', username)
+    properties = {}
+    properties['enable'] = self._bool2str(enable)
+    if enable is True:
+      properties['forwardTo'] = forward_to
+      properties['action'] = action
+    return self._PutProperties(uri, properties)
+
+  def UpdatePop(self, username, enable, enable_for=None, action=None):
+    """Update POP3 settings.
+
+    Args:
+      username: User to update POP3 settings for.
+      enable: Boolean whether to enable POP3.
+      enable_for: Which messages to make available via POP3.
+      action: Action to take after user retrieves email via POP3.
+
+    Returns:
+      A dict containing the result of the update operation.
+    """
+    uri = self._serviceUrl('pop', username)
+    properties = {}
+    properties['enable'] = self._bool2str(enable)
+    if enable is True:
+      properties['enableFor'] = enable_for
+      properties['action'] = action
+    return self._PutProperties(uri, properties)
+
+  def UpdateImap(self, username, enable):
+    """Update IMAP settings.
+
+    Args:
+      username: User to update IMAP settings for.
+      enable: Boolean whether to enable IMAP.
+
+    Returns:
+      A dict containing the result of the update operation.
+    """
+    uri = self._serviceUrl('imap', username)
+    properties = {'enable': self._bool2str(enable)}
+    return self._PutProperties(uri, properties)
+
+  def UpdateVacation(self, username, enable, subject=None, message=None,
+                     contacts_only=None):
+    """Update vacation settings.
+
+    Args:
+      username: User to update vacation settings for.
+      enable: Boolean whether to enable vacation responses.
+      subject: Vacation message subject.
+      message: Vacation message body.
+      contacts_only: Boolean whether to send message only to contacts.
+
+    Returns:
+      A dict containing the result of the update operation.
+    """
+    uri = self._serviceUrl('vacation', username)
+    properties = {}
+    properties['enable'] = self._bool2str(enable)
+    if enable is True:
+      properties['subject'] = subject
+      properties['message'] = message
+      properties['contactsOnly'] = self._bool2str(contacts_only)
+    return self._PutProperties(uri, properties)
+
+  def UpdateSignature(self, username, signature):
+    """Update signature.
+
+    Args:
+      username: User to update signature for.
+      signature: Signature string.
+
+    Returns:
+      A dict containing the result of the update operation.
+    """
+    uri = self._serviceUrl('signature', username)
+    properties = {'signature': signature}
+    return self._PutProperties(uri, properties)
+
+  def UpdateLanguage(self, username, language):
+    """Update user interface language.
+
+    Args:
+      username: User to update language for.
+      language: Language code.
+
+    Returns:
+      A dict containing the result of the update operation.
+    """
+    uri = self._serviceUrl('language', username)
+    properties = {'language': language}
+    return self._PutProperties(uri, properties)
+
+  def UpdateGeneral(self, username, page_size=None, shortcuts=None, arrows=None,
+                    snippets=None, unicode=None):
+    """Update general settings.
+
+    Args:
+      username: User to update general settings for.
+      page_size: Number of messages to show.
+      shortcuts: Boolean whether shortcuts are enabled.
+      arrows: Boolean whether arrows are enabled.
+      snippets: Boolean whether snippets are enabled.
+      unicode: Wheter unicode is enabled.
+
+    Returns:
+      A dict containing the result of the update operation.
+    """
+    uri = self._serviceUrl('general', username)
+    properties = {}
+    properties['pageSize'] = str(page_size)
+    properties['shortcuts'] = self._bool2str(shortcuts)
+    properties['arrows'] = self._bool2str(arrows)
+    properties['snippets'] = self._bool2str(snippets)
+    properties['unicode'] = self._bool2str(unicode)
+    return self._PutProperties(uri, properties)

Added: trunk/conduit/modules/GoogleModule/gdata/apps/migration/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/apps/migration/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,212 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Contains objects used with Google Apps."""
+
+__author__ = 'google-apps-apis googlegroups com'
+
+
+import atom
+import gdata
+
+
+# XML namespaces which are often used in Google Apps entity.
+APPS_NAMESPACE = 'http://schemas.google.com/apps/2006'
+APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s'
+
+
+class Rfc822Msg(atom.AtomBase):
+  """The Migration rfc822Msg element."""
+
+  _tag = 'rfc822Msg'
+  _namespace = APPS_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _attributes['encoding'] = 'encoding'
+
+  def __init__(self, extension_elements=None,
+               extension_attributes=None, text=None):
+    self.text = text
+    self.encoding = 'base64'
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def Rfc822MsgFromString(xml_string):
+  """Parse in the Rrc822 message from the XML definition."""
+
+  return atom.CreateClassFromXMLString(Rfc822Msg, xml_string)
+
+
+class MailItemProperty(atom.AtomBase):
+  """The Migration mailItemProperty element."""
+
+  _tag = 'mailItemProperty'
+  _namespace = APPS_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _attributes['value'] = 'value'
+
+  def __init__(self, value=None, extension_elements=None,
+               extension_attributes=None, text=None):
+    self.value = value
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def MailItemPropertyFromString(xml_string):
+  """Parse in the MailItemProperiy from the XML definition."""
+
+  return atom.CreateClassFromXMLString(MailItemProperty, xml_string)
+
+
+class Label(atom.AtomBase):
+  """The Migration label element."""
+
+  _tag = 'label'
+  _namespace = APPS_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _attributes['labelName'] = 'label_name'
+
+  def __init__(self, label_name=None,
+               extension_elements=None, extension_attributes=None,
+               text=None):
+    self.label_name = label_name
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def LabelFromString(xml_string):
+  """Parse in the mailItemProperty from the XML definition."""
+
+  return atom.CreateClassFromXMLString(Label, xml_string)
+
+
+class MailEntry(gdata.GDataEntry):
+  """A Google Migration flavor of an Atom Entry."""
+
+  _tag = 'entry'
+  _namespace = atom.ATOM_NAMESPACE
+  _children = gdata.GDataEntry._children.copy()
+  _attributes = gdata.GDataEntry._attributes.copy()
+  _children['{%s}rfc822Msg' % APPS_NAMESPACE] = ('rfc822_msg', Rfc822Msg)
+  _children['{%s}mailItemProperty' % APPS_NAMESPACE] = ('mail_item_property',
+                                                        [MailItemProperty])
+  _children['{%s}label' % APPS_NAMESPACE] = ('label', [Label])
+
+  def __init__(self, author=None, category=None, content=None,
+               atom_id=None, link=None, published=None,
+               title=None, updated=None,
+               rfc822_msg=None, mail_item_property=None, label=None,
+               extended_property=None,
+               extension_elements=None, extension_attributes=None, text=None):
+
+    gdata.GDataEntry.__init__(self, author=author, category=category,
+                              content=content,
+                              atom_id=atom_id, link=link, published=published,
+                              title=title, updated=updated)
+    self.rfc822_msg = rfc822_msg
+    self.mail_item_property = mail_item_property
+    self.label = label
+    self.extended_property = extended_property or []
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def MailEntryFromString(xml_string):
+  """Parse in the MailEntry from the XML definition."""
+
+  return atom.CreateClassFromXMLString(MailEntry, xml_string)
+
+
+class BatchMailEntry(gdata.BatchEntry):
+  """A Google Migration flavor of an Atom Entry."""
+
+  _tag = gdata.BatchEntry._tag
+  _namespace = gdata.BatchEntry._namespace
+  _children = gdata.BatchEntry._children.copy()
+  _attributes = gdata.BatchEntry._attributes.copy()
+  _children['{%s}rfc822Msg' % APPS_NAMESPACE] = ('rfc822_msg', Rfc822Msg)
+  _children['{%s}mailItemProperty' % APPS_NAMESPACE] = ('mail_item_property',
+                                                        [MailItemProperty])
+  _children['{%s}label' % APPS_NAMESPACE] = ('label', [Label])
+
+  def __init__(self, author=None, category=None, content=None,
+               atom_id=None, link=None, published=None,
+               title=None, updated=None,
+               rfc822_msg=None, mail_item_property=None, label=None,
+               batch_operation=None, batch_id=None, batch_status=None,
+               extended_property=None,
+               extension_elements=None, extension_attributes=None, text=None):
+
+    gdata.BatchEntry.__init__(self, author=author, category=category,
+                              content=content,
+                              atom_id=atom_id, link=link, published=published,
+                              batch_operation=batch_operation,
+                              batch_id=batch_id, batch_status=batch_status,
+                              title=title, updated=updated)
+    self.rfc822_msg = rfc822_msg or None
+    self.mail_item_property = mail_item_property or []
+    self.label = label or []
+    self.extended_property = extended_property or []
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def BatchMailEntryFromString(xml_string):
+  """Parse in the BatchMailEntry from the XML definition."""
+
+  return atom.CreateClassFromXMLString(BatchMailEntry, xml_string)
+
+
+class BatchMailEventFeed(gdata.BatchFeed):
+  """A Migration event feed flavor of an Atom Feed."""
+
+  _tag = gdata.BatchFeed._tag
+  _namespace = gdata.BatchFeed._namespace
+  _children = gdata.BatchFeed._children.copy()
+  _attributes = gdata.BatchFeed._attributes.copy()
+  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchMailEntry])
+
+  def __init__(self, author=None, category=None, contributor=None,
+               generator=None, icon=None, atom_id=None, link=None, logo=None,
+               rights=None, subtitle=None, title=None, updated=None,
+               entry=None, total_results=None, start_index=None,
+               items_per_page=None, interrupted=None, extension_elements=None,
+               extension_attributes=None, text=None):
+    gdata.BatchFeed.__init__(self, author=author, category=category,
+                             contributor=contributor, generator=generator,
+                             icon=icon, atom_id=atom_id, link=link,
+                             logo=logo, rights=rights, subtitle=subtitle,
+                             title=title, updated=updated, entry=entry,
+                             total_results=total_results,
+                             start_index=start_index,
+                             items_per_page=items_per_page,
+                             interrupted=interrupted,
+                             extension_elements=extension_elements,
+                             extension_attributes=extension_attributes,
+                             text=text)
+
+
+def BatchMailEventFeedFromString(xml_string):
+  """Parse in the BatchMailEventFeed from the XML definition."""
+
+  return atom.CreateClassFromXMLString(BatchMailEventFeed, xml_string)

Added: trunk/conduit/modules/GoogleModule/gdata/apps/migration/service.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/apps/migration/service.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Contains the methods to import mail via Google Apps Email Migration API.
+
+  MigrationService: Provides methids to import mail.
+"""
+
+__author__ = 'google-apps-apis googlegroups com'
+
+
+import base64
+import gdata
+import gdata.apps.service
+import gdata.service
+from gdata.apps import migration
+
+
+API_VER = '2.0'
+
+
+class MigrationService(gdata.apps.service.AppsService):
+  """Client for the EMAPI migration service.  Use either ImportMail to import
+  one message at a time, or AddBatchEntry and SubmitBatch to import a batch of
+  messages at a time.
+  """
+  def __init__(self, email=None, password=None, domain=None, source=None,
+               server='apps-apis.google.com', additional_headers=None):
+    gdata.apps.service.AppsService.__init__(
+        self, email=email, password=password, domain=domain, source=source,
+        server=server, additional_headers=additional_headers)
+    self.mail_batch = migration.BatchMailEventFeed()
+
+  def _BaseURL(self):
+    return '/a/feeds/migration/%s/%s' % (API_VER, self.domain)
+
+  def ImportMail(self, user_name, mail_message, mail_item_properties,
+                 mail_labels):
+    """Import a single mail message.
+
+    Args:
+      user_name: The username to import messages to.
+      mail_message: An RFC822 format email message.
+      mail_item_properties: A list of Gmail properties to apply to the message.
+      mail_labels: A list of labels to apply to the message.
+
+    Returns:
+      A MailEntry representing the successfully imported message.
+
+    Raises:
+      AppsForYourDomainException: An error occurred importing the message.
+    """
+    uri = '%s/%s/mail' % (self._BaseURL(), user_name)
+
+    mail_entry = migration.MailEntry()
+    mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode(
+        mail_message)))
+    mail_entry.rfc822_msg.encoding = 'base64'
+    mail_entry.mail_item_property = map(
+        lambda x: migration.MailItemProperty(value=x), mail_item_properties)
+    mail_entry.label = map(lambda x: migration.Label(label_name=x),
+                           mail_labels)
+
+    try:
+      return migration.MailEntryFromString(str(self.Post(mail_entry, uri)))
+    except gdata.service.RequestError, e:
+      raise gdata.apps.service.AppsForYourDomainException(e.args[0])
+
+  def AddBatchEntry(self, mail_message, mail_item_properties,
+                    mail_labels):
+    """Add a message to the current batch that you later will submit.
+
+    Args:
+      mail_message: An RFC822 format email message.
+      mail_item_properties: A list of Gmail properties to apply to the message.
+      mail_labels: A list of labels to apply to the message.
+
+    Returns:
+      The length of the MailEntry representing the message.
+    """
+    mail_entry = migration.BatchMailEntry()
+    mail_entry.rfc822_msg = migration.Rfc822Msg(text=(base64.b64encode(
+        mail_message)))
+    mail_entry.rfc822_msg.encoding = 'base64'
+    mail_entry.mail_item_property = map(
+        lambda x: migration.MailItemProperty(value=x), mail_item_properties)
+    mail_entry.label = map(lambda x: migration.Label(label_name=x),
+                           mail_labels)
+
+    self.mail_batch.AddBatchEntry(mail_entry)
+
+    return len(str(mail_entry))
+
+  def SubmitBatch(self, user_name):
+    """Send a all the mail items you have added to the batch to the server.
+
+    Args:
+      user_name: The username to import messages to.
+
+    Returns:
+      A HTTPResponse from the web service call.
+
+    Raises:
+      AppsForYourDomainException: An error occurred importing the batch.
+    """
+    uri = '%s/%s/mail/batch' % (self._BaseURL(), user_name)
+
+    try:
+      self.result = self.Post(self.mail_batch, uri,
+                              converter=migration.BatchMailEventFeedFromString)
+    except gdata.service.RequestError, e:
+      raise gdata.apps.service.AppsForYourDomainException(e.args[0])
+
+    self.mail_batch = migration.BatchMailEventFeed()
+
+    return self.result

Modified: trunk/conduit/modules/GoogleModule/gdata/apps/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/apps/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/apps/service.py	Tue Mar 17 09:00:35 2009
@@ -66,7 +66,7 @@
 
   def __init__(self, response):
 
-    self.args = response
+    Error.__init__(self, response)
     try:
       self.element_tree = ElementTree.fromstring(response['body'])
       self.error_code = int(self.element_tree[0].attrib['errorCode'])
@@ -79,11 +79,24 @@
   """Client for the Google Apps Provisioning service."""
 
   def __init__(self, email=None, password=None, domain=None, source=None,
-               server='www.google.com', additional_headers=None):
-    gdata.service.GDataService.__init__(self, email=email, password=password,
-                                        service='apps', source=source,
-                                        server=server,
-                                        additional_headers=additional_headers)
+               server='apps-apis.google.com', additional_headers=None,
+               **kwargs):
+    """Creates a client for the Google Apps Provisioning service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      domain: string (optional) The Google Apps domain name.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'apps-apis.google.com'.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='apps', source=source,
+        server=server, additional_headers=additional_headers, **kwargs)
     self.ssl = True
     self.port = 443
     self.domain = domain
@@ -91,7 +104,7 @@
   def _baseURL(self):
     return "/a/feeds/%s" % self.domain 
 
-  def GetGenaratorFromLinkFinder(self, link_finder, func):
+  def GetGeneratorFromLinkFinder(self, link_finder, func):
     """returns a generator for pagination"""
     yield link_finder
     next = link_finder.GetNextLink()
@@ -372,7 +385,7 @@
   def GetGeneratorForAllUsers(self):
     """Retrieve a generator for all users in this domain."""
     first_page = self.RetrievePageOfUsers()
-    return self.GetGenaratorFromLinkFinder(first_page,
+    return self.GetGeneratorFromLinkFinder(first_page,
                                            gdata.apps.UserFeedFromString)
 
   def RetrieveAllUsers(self):
@@ -383,3 +396,54 @@
     return self.AddAllElementsFromAllPages(
       ret, gdata.apps.UserFeedFromString)
 
+
+class PropertyService(gdata.service.GDataService):
+  """Client for the Google Apps Property service."""
+
+  def __init__(self, email=None, password=None, domain=None, source=None,
+               server='apps-apis.google.com', additional_headers=None):
+    gdata.service.GDataService.__init__(self, email=email, password=password,
+                                        service='apps', source=source,
+                                        server=server,
+                                        additional_headers=additional_headers)
+    self.ssl = True
+    self.port = 443
+    self.domain = domain
+
+  def _GetPropertyEntry(self, properties):
+    property_entry = gdata.apps.PropertyEntry()
+    property = []
+    for name, value in properties.iteritems():
+      if name is not None and value is not None:
+        property.append(gdata.apps.Property(name=name, value=value))
+    property_entry.property = property
+    return property_entry
+
+  def _PropertyEntry2Dict(self, property_entry):
+    properties = {}
+    for i, property in enumerate(property_entry.property):
+      properties[property.name] = property.value
+    return properties
+
+  def _GetProperties(self, uri):
+    try:
+      return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
+        str(self.Get(uri))))
+    except gdata.service.RequestError, e:
+      raise gdata.apps.service.AppsForYourDomainException(e.args[0])
+
+  def _PostProperties(self, uri, properties):
+    property_entry = self._GetPropertyEntry(properties)
+    try:
+      return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
+        str(self.Post(property_entry, uri))))
+    except gdata.service.RequestError, e:
+      raise gdata.apps.service.AppsForYourDomainException(e.args[0])
+
+  def _PutProperties(self, uri, properties):
+    property_entry = self._GetPropertyEntry(properties)
+    try:
+      return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
+        str(self.Put(property_entry, uri))))
+    except gdata.service.RequestError, e:
+      raise gdata.apps.service.AppsForYourDomainException(e.args[0])

Modified: trunk/conduit/modules/GoogleModule/gdata/auth.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/auth.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/auth.py	Tue Mar 17 09:00:35 2009
@@ -1,6 +1,6 @@
-#/usr/bin/python
+#!/usr/bin/python
 #
-# Copyright (C) 2007 Google Inc.
+# Copyright (C) 2007, 2008 Google Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,17 +15,61 @@
 # limitations under the License.
 
 
+import cgi
+import math
+import random
 import re
+import time
+import types
 import urllib
+import atom.http_interface
+import atom.token_store
+import atom.url
+import gdata.oauth as oauth
+import gdata.oauth.rsa as oauth_rsa
+import gdata.tlslite.utils.keyfactory as keyfactory
+import gdata.tlslite.utils.cryptomath as cryptomath
+
+__author__ = 'api.jscudder (Jeff Scudder)'
+
+
+PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth='
+AUTHSUB_AUTH_LABEL = 'AuthSub token='
+
+
+"""This module provides functions and objects used with Google authentication.
+
+Details on Google authorization mechanisms used with the Google Data APIs can
+be found here: 
+http://code.google.com/apis/gdata/auth.html
+http://code.google.com/apis/accounts/
+
+The essential functions are the following.
+Related to ClientLogin:
+  generate_client_login_request_body: Constructs the body of an HTTP request to
+                                      obtain a ClientLogin token for a specific
+                                      service. 
+  extract_client_login_token: Creates a ClientLoginToken with the token from a
+                              success response to a ClientLogin request.
+  get_captcha_challenge: If the server responded to the ClientLogin request
+                         with a CAPTCHA challenge, this method extracts the
+                         CAPTCHA URL and identifying CAPTCHA token.
+
+Related to AuthSub:
+  generate_auth_sub_url: Constructs a full URL for a AuthSub request. The 
+                         user's browser must be sent to this Google Accounts
+                         URL and redirected back to the app to obtain the
+                         AuthSub token.
+  extract_auth_sub_token_from_url: Once the user's browser has been 
+                                   redirected back to the web app, use this
+                                   function to create an AuthSubToken with
+                                   the correct authorization token and scope.
+  token_from_http_body: Extracts the AuthSubToken value string from the 
+                        server's response to an AuthSub session token upgrade
+                        request.
+"""
 
-
-__author__ = 'api.jscudder (Jeffrey Scudder)'
-
-
-AUTH_SUB_KEY_PATTERN = re.compile('.*\?.*token=(.*)(&?)')
-
-
-def GenerateClientLoginRequestBody(email, password, service, source, 
+def generate_client_login_request_body(email, password, service, source, 
     account_type='HOSTED_OR_GOOGLE', captcha_token=None, 
     captcha_response=None):
   """Creates the body of the autentication request
@@ -60,6 +104,9 @@
   return urllib.urlencode(request_fields)
 
 
+GenerateClientLoginRequestBody = generate_client_login_request_body
+
+
 def GenerateClientLoginAuthToken(http_body):
   """Returns the token value to use in Authorization headers.
 
@@ -73,16 +120,56 @@
   Returns:
     The value half of an Authorization header.
   """
+  token = get_client_login_token(http_body)
+  if token:
+    return 'GoogleLogin auth=%s' % token
+  return None
+
+
+def get_client_login_token(http_body):
+  """Returns the token value for a ClientLoginToken.
+
+  Reads the token from the server's response to a Client Login request and
+  creates the token value string to use in requests.
+
+  Args:
+    http_body: str The body of the server's HTTP response to a Client Login
+        request
+ 
+  Returns:
+    The token value string for a ClientLoginToken.
+  """
   for response_line in http_body.splitlines():
     if response_line.startswith('Auth='):
       # Strip off the leading Auth= and return the Authorization value.
-      return 'GoogleLogin auth=%s' % response_line[5:]
+      return response_line[5:]
   return None
 
 
-def GetCaptchChallenge(http_body, 
+def extract_client_login_token(http_body, scopes):
+  """Parses the server's response and returns a ClientLoginToken.
+  
+  Args:
+    http_body: str The body of the server's HTTP response to a Client Login
+               request. It is assumed that the login request was successful.
+    scopes: list containing atom.url.Urls or strs. The scopes list contains
+            all of the partial URLs under which the client login token is
+            valid. For example, if scopes contains ['http://example.com/foo']
+            then the client login token would be valid for 
+            http://example.com/foo/bar/baz
+
+  Returns:
+    A ClientLoginToken which is valid for the specified scopes.
+  """
+  token_string = get_client_login_token(http_body)
+  token = ClientLoginToken(scopes=scopes)
+  token.set_token_string(token_string)
+  return token
+
+
+def get_captcha_challenge(http_body, 
     captcha_base_url='http://www.google.com/accounts/'):
-  """Returns the URL and token for a CAPTCHA challenge issued bu the server.
+  """Returns the URL and token for a CAPTCHA challenge issued by the server.
 
   Args:
     http_body: str The body of the HTTP response from the server which 
@@ -119,8 +206,123 @@
     return None
 
 
+GetCaptchaChallenge = get_captcha_challenge
+
+
+def GenerateOAuthRequestTokenUrl(
+    oauth_input_params, scopes,
+    request_token_url='https://www.google.com/accounts/OAuthGetRequestToken',
+    extra_parameters=None):
+  """Generate a URL at which a request for OAuth request token is to be sent.
+  
+  Args:
+    oauth_input_params: OAuthInputParams OAuth input parameters.
+    scopes: list of strings The URLs of the services to be accessed.
+    request_token_url: string The beginning of the request token URL. This is
+        normally 'https://www.google.com/accounts/OAuthGetRequestToken' or
+        '/accounts/OAuthGetRequestToken'
+    extra_parameters: dict (optional) key-value pairs as any additional
+        parameters to be included in the URL and signature while making a
+        request for fetching an OAuth request token. All the OAuth parameters
+        are added by default. But if provided through this argument, any
+        default parameters will be overwritten. For e.g. a default parameter
+        oauth_version 1.0 can be overwritten if
+        extra_parameters = {'oauth_version': '2.0'}
+  
+  Returns:
+    atom.url.Url OAuth request token URL.
+  """
+  scopes_string = ' '.join([str(scope) for scope in scopes])
+  parameters = {'scope': scopes_string}
+  if extra_parameters:
+    parameters.update(extra_parameters)
+  oauth_request = oauth.OAuthRequest.from_consumer_and_token(
+      oauth_input_params.GetConsumer(), http_url=request_token_url,
+      parameters=parameters)
+  oauth_request.sign_request(oauth_input_params.GetSignatureMethod(),
+                             oauth_input_params.GetConsumer(), None)
+  return atom.url.parse_url(oauth_request.to_url())
+
+
+def GenerateOAuthAuthorizationUrl(
+    request_token,
+    authorization_url='https://www.google.com/accounts/OAuthAuthorizeToken',
+    callback_url=None, extra_params=None,
+    include_scopes_in_callback=False, scopes_param_prefix='oauth_token_scope'):
+  """Generates URL at which user will login to authorize the request token.
+  
+  Args:
+    request_token: gdata.auth.OAuthToken OAuth request token.
+    authorization_url: string The beginning of the authorization URL. This is
+        normally 'https://www.google.com/accounts/OAuthAuthorizeToken' or
+        '/accounts/OAuthAuthorizeToken'
+    callback_url: string (optional) The URL user will be sent to after
+        logging in and granting access.
+    extra_params: dict (optional) Additional parameters to be sent.
+    include_scopes_in_callback: Boolean (default=False) if set to True, and
+        if 'callback_url' is present, the 'callback_url' will be modified to
+        include the scope(s) from the request token as a URL parameter. The
+        key for the 'callback' URL's scope parameter will be
+        OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as
+        a parameter to the 'callback' URL, is that the page which receives
+        the OAuth token will be able to tell which URLs the token grants
+        access to.
+    scopes_param_prefix: string (default='oauth_token_scope') The URL
+        parameter key which maps to the list of valid scopes for the token.
+        This URL parameter will be included in the callback URL along with
+        the scopes of the token as value if include_scopes_in_callback=True.
+
+  Returns:
+    atom.url.Url OAuth authorization URL.
+  """
+  scopes = request_token.scopes
+  if isinstance(scopes, list):
+    scopes = ' '.join(scopes)  
+  if include_scopes_in_callback and callback_url:
+    if callback_url.find('?') > -1:
+      callback_url += '&'
+    else:
+      callback_url += '?'
+    callback_url += urllib.urlencode({scopes_param_prefix:scopes})  
+  oauth_token = oauth.OAuthToken(request_token.key, request_token.secret)
+  oauth_request = oauth.OAuthRequest.from_token_and_callback(
+      token=oauth_token, callback=callback_url,
+      http_url=authorization_url, parameters=extra_params)
+  return atom.url.parse_url(oauth_request.to_url())
+
+
+def GenerateOAuthAccessTokenUrl(
+    authorized_request_token,
+    oauth_input_params,
+    access_token_url='https://www.google.com/accounts/OAuthGetAccessToken',
+    oauth_version='1.0'):
+  """Generates URL at which user will login to authorize the request token.
+  
+  Args:
+    authorized_request_token: gdata.auth.OAuthToken OAuth authorized request
+        token.
+    oauth_input_params: OAuthInputParams OAuth input parameters.    
+    access_token_url: string The beginning of the authorization URL. This is
+        normally 'https://www.google.com/accounts/OAuthGetAccessToken' or
+        '/accounts/OAuthGetAccessToken'
+    oauth_version: str (default='1.0') oauth_version parameter.
+
+  Returns:
+    atom.url.Url OAuth access token URL.
+  """
+  oauth_token = oauth.OAuthToken(authorized_request_token.key,
+                                 authorized_request_token.secret)
+  oauth_request = oauth.OAuthRequest.from_consumer_and_token(
+      oauth_input_params.GetConsumer(), token=oauth_token,
+      http_url=access_token_url, parameters={'oauth_version': oauth_version})
+  oauth_request.sign_request(oauth_input_params.GetSignatureMethod(),
+                             oauth_input_params.GetConsumer(), oauth_token)
+  return atom.url.parse_url(oauth_request.to_url())
+
+
 def GenerateAuthSubUrl(next, scope, secure=False, session=True, 
-    request_url='https://www.google.com/accounts/AuthSubRequest'):
+    request_url='https://www.google.com/accounts/AuthSubRequest',
+    domain='default'):
   """Generate a URL at which the user will login and be redirected back.
 
   Users enter their credentials on a Google login page and a token is sent
@@ -137,6 +339,9 @@
             is a secure token.
     session: boolean (optional) Determines whether or not the issued token
              can be upgraded to a session token.
+    domain: str (optional) The Google Apps domain for this account. If this
+            is not a Google Apps account, use 'default' which is the default
+            value.
   """
   # Translate True/False values for parameters into numeric values acceoted
   # by the AuthSub service.
@@ -151,7 +356,8 @@
     session = 0
 
   request_params = urllib.urlencode({'next': next, 'scope': scope,
-                                  'secure': secure, 'session': session})
+                                     'secure': secure, 'session': session, 
+                                     'hd': domain})
   if request_url.find('?') == -1:
     return '%s?%s' % (request_url, request_params)
   else:
@@ -160,30 +366,148 @@
     return '%s&%s' % (request_url, request_params)
 
 
+def generate_auth_sub_url(next, scopes, secure=False, session=True,
+    request_url='https://www.google.com/accounts/AuthSubRequest', 
+    domain='default', scopes_param_prefix='auth_sub_scopes'):
+  """Constructs a URL string for requesting a multiscope AuthSub token.
+
+  The generated token will contain a URL parameter to pass along the 
+  requested scopes to the next URL. When the Google Accounts page 
+  redirects the broswser to the 'next' URL, it appends the single use
+  AuthSub token value to the URL as a URL parameter with the key 'token'.
+  However, the information about which scopes were requested is not
+  included by Google Accounts. This method adds the scopes to the next
+  URL before making the request so that the redirect will be sent to 
+  a page, and both the token value and the list of scopes can be 
+  extracted from the request URL. 
+
+  Args:
+    next: atom.url.URL or string The URL user will be sent to after
+          authorizing this web application to access their data.
+    scopes: list containint strings The URLs of the services to be accessed.
+    secure: boolean (optional) Determines whether or not the issued token
+            is a secure token.
+    session: boolean (optional) Determines whether or not the issued token
+             can be upgraded to a session token.
+    request_url: atom.url.Url or str The beginning of the request URL. This
+        is normally 'http://www.google.com/accounts/AuthSubRequest' or 
+        '/accounts/AuthSubRequest'
+    domain: The domain which the account is part of. This is used for Google
+        Apps accounts, the default value is 'default' which means that the
+        requested account is a Google Account (@gmail.com for example)
+    scopes_param_prefix: str (optional) The requested scopes are added as a 
+        URL parameter to the next URL so that the page at the 'next' URL can
+        extract the token value and the valid scopes from the URL. The key
+        for the URL parameter defaults to 'auth_sub_scopes'
+
+  Returns:
+    An atom.url.Url which the user's browser should be directed to in order
+    to authorize this application to access their information.
+  """
+  if isinstance(next, (str, unicode)):
+    next = atom.url.parse_url(next)
+  scopes_string = ' '.join([str(scope) for scope in scopes])
+  next.params[scopes_param_prefix] = scopes_string
+
+  if isinstance(request_url, (str, unicode)):
+    request_url = atom.url.parse_url(request_url)
+  request_url.params['next'] = str(next)
+  request_url.params['scope'] = scopes_string
+  if session:
+    request_url.params['session'] = 1
+  else:
+    request_url.params['session'] = 0
+  if secure:
+    request_url.params['secure'] = 1
+  else:
+    request_url.params['secure'] = 0
+  request_url.params['hd'] = domain
+  return request_url
+
+
 def AuthSubTokenFromUrl(url):
   """Extracts the AuthSub token from the URL. 
 
   Used after the AuthSub redirect has sent the user to the 'next' page and
-  appended the token to the URL.
+  appended the token to the URL. This function returns the value to be used
+  in the Authorization header. 
 
   Args:
     url: str The URL of the current page which contains the AuthSub token as
         a URL parameter.
   """
-  m = AUTH_SUB_KEY_PATTERN.match(url)
-  if m:
-    return 'AuthSub token=%s' % m.group(1)
+  token = TokenFromUrl(url)
+  if token:
+    return 'AuthSub token=%s' % token
+  return None
+
+
+def TokenFromUrl(url):
+  """Extracts the AuthSub token from the URL.
+
+  Returns the raw token value.
+
+  Args:
+    url: str The URL or the query portion of the URL string (after the ?) of
+        the current page which contains the AuthSub token as a URL parameter.
+  """
+  if url.find('?') > -1:
+    query_params = url.split('?')[1]
+  else:
+    query_params = url
+  for pair in query_params.split('&'):
+    if pair.startswith('token='):
+      return pair[6:]
   return None
 
 
+def extract_auth_sub_token_from_url(url, 
+    scopes_param_prefix='auth_sub_scopes', rsa_key=None):
+  """Creates an AuthSubToken and sets the token value and scopes from the URL.
+  
+  After the Google Accounts AuthSub pages redirect the user's broswer back to 
+  the web application (using the 'next' URL from the request) the web app must
+  extract the token from the current page's URL. The token is provided as a 
+  URL parameter named 'token' and if generate_auth_sub_url was used to create
+  the request, the token's valid scopes are included in a URL parameter whose
+  name is specified in scopes_param_prefix.
+
+  Args:
+    url: atom.url.Url or str representing the current URL. The token value
+         and valid scopes should be included as URL parameters.
+    scopes_param_prefix: str (optional) The URL parameter key which maps to
+                         the list of valid scopes for the token.
+
+  Returns:
+    An AuthSubToken with the token value from the URL and set to be valid for
+    the scopes passed in on the URL. If no scopes were included in the URL,
+    the AuthSubToken defaults to being valid for no scopes. If there was no
+    'token' parameter in the URL, this function returns None.
+  """
+  if isinstance(url, (str, unicode)):
+    url = atom.url.parse_url(url)
+  if 'token' not in url.params:
+    return None
+  scopes = []
+  if scopes_param_prefix in url.params:
+    scopes = url.params[scopes_param_prefix].split(' ')
+  token_value = url.params['token']
+  if rsa_key:
+    token = SecureAuthSubToken(rsa_key, scopes=scopes)
+  else:
+    token = AuthSubToken(scopes=scopes)
+  token.set_token_string(token_value)
+  return token
+
+
 def AuthSubTokenFromHttpBody(http_body):
   """Extracts the AuthSub token from an HTTP body string.
 
-  Used to find the new session token after making a request to upgrade a 
+  Used to find the new session token after making a request to upgrade a
   single use AuthSub token.
 
   Args:
-    http_body: str The repsonse from the server which contains the AuthSub 
+    http_body: str The repsonse from the server which contains the AuthSub
         key. For example, this function would find the new session token
         from the server's response to an upgrade token request.
 
@@ -191,9 +515,415 @@
     The header value to use for Authorization which contains the AuthSub
     token.
   """
+  token_value = token_from_http_body(http_body)
+  if token_value:
+    return '%s%s' % (AUTHSUB_AUTH_LABEL, token_value)
+  return None
+
+
+def token_from_http_body(http_body):
+  """Extracts the AuthSub token from an HTTP body string.
+
+  Used to find the new session token after making a request to upgrade a 
+  single use AuthSub token.
+
+  Args:
+    http_body: str The repsonse from the server which contains the AuthSub 
+        key. For example, this function would find the new session token
+        from the server's response to an upgrade token request.
+
+  Returns:
+    The raw token value to use in an AuthSubToken object.
+  """
   for response_line in http_body.splitlines():
     if response_line.startswith('Token='):
-      # Strip off Token= and construct the Authorization value.
-      auth_token = response_line[6:]
-      return 'AuthSub token=%s' % auth_token
+      # Strip off Token= and return the token value string.
+      return response_line[6:]
   return None
+
+
+TokenFromHttpBody = token_from_http_body
+
+
+def OAuthTokenFromUrl(url, scopes_param_prefix='oauth_token_scope'):
+  """Creates an OAuthToken and sets token key and scopes (if present) from URL.
+  
+  After the Google Accounts OAuth pages redirect the user's broswer back to 
+  the web application (using the 'callback' URL from the request) the web app
+  can extract the token from the current page's URL. The token is same as the
+  request token, but it is either authorized (if user grants access) or
+  unauthorized (if user denies access). The token is provided as a 
+  URL parameter named 'oauth_token' and if it was chosen to use
+  GenerateOAuthAuthorizationUrl with include_scopes_in_param=True, the token's
+  valid scopes are included in a URL parameter whose name is specified in
+  scopes_param_prefix.
+
+  Args:
+    url: atom.url.Url or str representing the current URL. The token value
+        and valid scopes should be included as URL parameters.
+    scopes_param_prefix: str (optional) The URL parameter key which maps to
+        the list of valid scopes for the token.
+
+  Returns:
+    An OAuthToken with the token key from the URL and set to be valid for
+    the scopes passed in on the URL. If no scopes were included in the URL,
+    the OAuthToken defaults to being valid for no scopes. If there was no
+    'oauth_token' parameter in the URL, this function returns None.
+  """
+  if isinstance(url, (str, unicode)):
+    url = atom.url.parse_url(url)
+  if 'oauth_token' not in url.params:
+    return None
+  scopes = []
+  if scopes_param_prefix in url.params:
+    scopes = url.params[scopes_param_prefix].split(' ')
+  token_key = url.params['oauth_token']
+  token = OAuthToken(key=token_key, scopes=scopes)
+  return token
+
+
+def OAuthTokenFromHttpBody(http_body):
+  """Parses the HTTP response body and returns an OAuth token.
+  
+  The returned OAuth token will just have key and secret parameters set.
+  It won't have any knowledge about the scopes or oauth_input_params. It is
+  your responsibility to make it aware of the remaining parameters.
+  
+  Returns:
+    OAuthToken OAuth token.
+  """
+  token = oauth.OAuthToken.from_string(http_body)
+  oauth_token = OAuthToken(key=token.key, secret=token.secret)
+  return oauth_token
+  
+
+class OAuthSignatureMethod(object):
+  """Holds valid OAuth signature methods.
+  
+  RSA_SHA1: Class to build signature according to RSA-SHA1 algorithm.
+  HMAC_SHA1: Class to build signature according to HMAC-SHA1 algorithm.
+  """
+  
+  HMAC_SHA1 = oauth.OAuthSignatureMethod_HMAC_SHA1  
+  
+  class RSA_SHA1(oauth_rsa.OAuthSignatureMethod_RSA_SHA1):
+    """Provides implementation for abstract methods to return RSA certs."""
+
+    def __init__(self, private_key, public_cert):
+      self.private_key = private_key
+      self.public_cert = public_cert
+  
+    def _fetch_public_cert(self, unused_oauth_request):
+      return self.public_cert
+  
+    def _fetch_private_cert(self, unused_oauth_request):
+      return self.private_key
+  
+
+class OAuthInputParams(object):
+  """Stores OAuth input parameters.
+  
+  This class is a store for OAuth input parameters viz. consumer key and secret,
+  signature method and RSA key.
+  """
+  
+  def __init__(self, signature_method, consumer_key, consumer_secret=None,
+               rsa_key=None):
+    """Initializes object with parameters required for using OAuth mechanism.
+    
+    NOTE: Though consumer_secret and rsa_key are optional, either of the two
+    is required depending on the value of the signature_method.
+    
+    Args:
+      signature_method: class which provides implementation for strategy class
+          oauth.oauth.OAuthSignatureMethod. Signature method to be used for
+          signing each request. Valid implementations are provided as the
+          constants defined by gdata.auth.OAuthSignatureMethod. Currently
+          they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and
+          gdata.auth.OAuthSignatureMethod.HMAC_SHA1
+      consumer_key: string Domain identifying third_party web application.
+      consumer_secret: string (optional) Secret generated during registration.
+          Required only for HMAC_SHA1 signature method.
+      rsa_key: string (optional) Private key required for RSA_SHA1 signature
+          method.
+    """
+    if signature_method == OAuthSignatureMethod.RSA_SHA1:
+      self._signature_method = signature_method(rsa_key, None)
+    else:
+      self._signature_method = signature_method()
+    self._consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
+    
+  def GetSignatureMethod(self):
+    """Gets the OAuth signature method.
+
+    Returns:
+      object of supertype <oauth.oauth.OAuthSignatureMethod>
+    """
+    return self._signature_method
+  
+  def GetConsumer(self):
+    """Gets the OAuth consumer.
+    
+    Returns:
+      object of type <oauth.oauth.Consumer>
+    """
+    return self._consumer
+
+
+class ClientLoginToken(atom.http_interface.GenericToken):
+  """Stores the Authorization header in auth_header and adds to requests.
+
+  This token will add it's Authorization header to an HTTP request
+  as it is made. Ths token class is simple but
+  some Token classes must calculate portions of the Authorization header
+  based on the request being made, which is why the token is responsible
+  for making requests via an http_client parameter.
+
+  Args:
+    auth_header: str The value for the Authorization header.
+    scopes: list of str or atom.url.Url specifying the beginnings of URLs
+        for which this token can be used. For example, if scopes contains
+        'http://example.com/foo', then this token can be used for a request to
+        'http://example.com/foo/bar' but it cannot be used for a request to
+        'http://example.com/baz'
+  """
+  def __init__(self, auth_header=None, scopes=None):
+    self.auth_header = auth_header
+    self.scopes = scopes or []
+
+  def __str__(self):
+    return self.auth_header
+
+  def perform_request(self, http_client, operation, url, data=None,
+                      headers=None):
+    """Sets the Authorization header and makes the HTTP request."""
+    if headers is None:
+      headers = {'Authorization':self.auth_header}
+    else:
+      headers['Authorization'] = self.auth_header
+    return http_client.request(operation, url, data=data, headers=headers)
+
+  def get_token_string(self):
+    """Removes PROGRAMMATIC_AUTH_LABEL to give just the token value."""
+    return self.auth_header[len(PROGRAMMATIC_AUTH_LABEL):]
+
+  def set_token_string(self, token_string):
+    self.auth_header = '%s%s' % (PROGRAMMATIC_AUTH_LABEL, token_string)
+  
+  def valid_for_scope(self, url):
+    """Tells the caller if the token authorizes access to the desired URL.
+    """
+    if isinstance(url, (str, unicode)):
+      url = atom.url.parse_url(url)
+    for scope in self.scopes:
+      if scope == atom.token_store.SCOPE_ALL:
+        return True
+      if isinstance(scope, (str, unicode)):
+        scope = atom.url.parse_url(scope)
+      if scope == url:
+        return True
+      # Check the host and the path, but ignore the port and protocol.
+      elif scope.host == url.host and not scope.path:
+        return True
+      elif scope.host == url.host and scope.path and not url.path:
+        continue
+      elif scope.host == url.host and url.path.startswith(scope.path):
+        return True
+    return False
+
+
+class AuthSubToken(ClientLoginToken):
+  def get_token_string(self):
+    """Removes AUTHSUB_AUTH_LABEL to give just the token value."""
+    return self.auth_header[len(AUTHSUB_AUTH_LABEL):]
+
+  def set_token_string(self, token_string):
+    self.auth_header = '%s%s' % (AUTHSUB_AUTH_LABEL, token_string)
+
+
+class OAuthToken(atom.http_interface.GenericToken):
+  """Stores the token key, token secret and scopes for which token is valid.
+  
+  This token adds the authorization header to each request made. It
+  re-calculates authorization header for every request since the OAuth
+  signature to be added to the authorization header is dependent on the
+  request parameters.
+  
+  Attributes:
+    key: str The value for the OAuth token i.e. token key.
+    secret: str The value for the OAuth token secret.
+    scopes: list of str or atom.url.Url specifying the beginnings of URLs
+        for which this token can be used. For example, if scopes contains
+        'http://example.com/foo', then this token can be used for a request to
+        'http://example.com/foo/bar' but it cannot be used for a request to
+        'http://example.com/baz'
+    oauth_input_params: OAuthInputParams OAuth input parameters.      
+  """
+  
+  def __init__(self, key=None, secret=None, scopes=None,
+               oauth_input_params=None):
+    self.key = key
+    self.secret = secret
+    self.scopes = scopes or []
+    self.oauth_input_params = oauth_input_params
+  
+  def __str__(self):
+    return self.get_token_string()
+
+  def get_token_string(self):
+    """Returns the token string.
+    
+    The token string returned is of format
+    oauth_token=[0]&oauth_token_secret=[1], where [0] and [1] are some strings.
+    
+    Returns:
+      A token string of format oauth_token=[0]&oauth_token_secret=[1],
+      where [0] and [1] are some strings. If self.secret is absent, it just
+      returns oauth_token=[0]. If self.key is absent, it just returns
+      oauth_token_secret=[1]. If both are absent, it returns None.
+    """
+    if self.key and self.secret:
+      return urllib.urlencode({'oauth_token': self.key,
+                               'oauth_token_secret': self.secret})
+    elif self.key:
+      return 'oauth_token=%s' % self.key
+    elif self.secret:
+      return 'oauth_token_secret=%s' % self.secret
+    else:
+      return None
+
+  def set_token_string(self, token_string):
+    """Sets the token key and secret from the token string.
+    
+    Args:
+      token_string: str Token string of form
+          oauth_token=[0]&oauth_token_secret=[1]. If oauth_token is not present,
+          self.key will be None. If oauth_token_secret is not present,
+          self.secret will be None.
+    """
+    token_params = cgi.parse_qs(token_string, keep_blank_values=False)
+    if 'oauth_token' in token_params:
+      self.key = token_params['oauth_token'][0]
+    if 'oauth_token_secret' in token_params:
+      self.secret = token_params['oauth_token_secret'][0]
+    
+  def GetAuthHeader(self, http_method, http_url, realm=''):
+    """Get the authentication header.
+
+    Args:
+      http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc.
+      http_url: string or atom.url.Url HTTP URL to which request is made.
+      realm: string (default='') realm parameter to be included in the
+          authorization header.
+
+    Returns:
+      dict Header to be sent with every subsequent request after
+      authentication.
+    """
+    if isinstance(http_url, types.StringTypes):
+      http_url = atom.url.parse_url(http_url)
+    header = None
+    token = None
+    if self.key or self.secret:
+      token = oauth.OAuthToken(self.key, self.secret)
+    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
+        self.oauth_input_params.GetConsumer(), token=token,
+        http_url=str(http_url), http_method=http_method,
+        parameters=http_url.params)
+    oauth_request.sign_request(self.oauth_input_params.GetSignatureMethod(),
+                               self.oauth_input_params.GetConsumer(), token)
+    header = oauth_request.to_header(realm=realm)
+    header['Authorization'] = header['Authorization'].replace('+', '%2B')
+    return header
+  
+  def perform_request(self, http_client, operation, url, data=None, 
+                      headers=None):
+    """Sets the Authorization header and makes the HTTP request."""
+    if not headers:
+      headers = {}
+    headers.update(self.GetAuthHeader(operation, url))
+    return http_client.request(operation, url, data=data, headers=headers)
+    
+  def valid_for_scope(self, url):
+    if isinstance(url, (str, unicode)):
+      url = atom.url.parse_url(url)
+    for scope in self.scopes:
+      if scope == atom.token_store.SCOPE_ALL:
+        return True
+      if isinstance(scope, (str, unicode)):
+        scope = atom.url.parse_url(scope)
+      if scope == url:
+        return True
+      # Check the host and the path, but ignore the port and protocol.
+      elif scope.host == url.host and not scope.path:
+        return True
+      elif scope.host == url.host and scope.path and not url.path:
+        continue
+      elif scope.host == url.host and url.path.startswith(scope.path):
+        return True
+    return False    
+    
+
+class SecureAuthSubToken(AuthSubToken):
+  """Stores the rsa private key, token, and scopes for the secure AuthSub token.
+  
+  This token adds the authorization header to each request made. It
+  re-calculates authorization header for every request since the secure AuthSub
+  signature to be added to the authorization header is dependent on the
+  request parameters.
+  
+  Attributes:
+    rsa_key: string The RSA private key in PEM format that the token will
+             use to sign requests
+    token_string: string (optional) The value for the AuthSub token.
+    scopes: list of str or atom.url.Url specifying the beginnings of URLs
+        for which this token can be used. For example, if scopes contains
+        'http://example.com/foo', then this token can be used for a request to
+        'http://example.com/foo/bar' but it cannot be used for a request to
+        'http://example.com/baz'     
+  """
+  
+  def __init__(self, rsa_key, token_string=None, scopes=None):
+    self.rsa_key = keyfactory.parsePEMKey(rsa_key)
+    self.token_string = token_string or ''
+    self.scopes = scopes or [] 
+   
+  def __str__(self):
+    return self.get_token_string()
+
+  def get_token_string(self):
+    return str(self.token_string)
+
+  def set_token_string(self, token_string):
+    self.token_string = token_string
+    
+  def GetAuthHeader(self, http_method, http_url):
+    """Generates the Authorization header.
+
+    The form of the secure AuthSub Authorization header is
+    Authorization: AuthSub token="token" sigalg="sigalg" data="data" sig="sig"
+    and  data represents a string in the form
+    data = http_method http_url timestamp nonce
+
+    Args:
+      http_method: string HTTP method i.e. operation e.g. GET, POST, PUT, etc.
+      http_url: string or atom.url.Url HTTP URL to which request is made.
+      
+    Returns:
+      dict Header to be sent with every subsequent request after authentication.
+    """
+    timestamp = int(math.floor(time.time()))
+    nonce = '%lu' % random.randrange(1, 2**64)
+    data = '%s %s %d %s' % (http_method, str(http_url), timestamp, nonce)
+    sig = cryptomath.bytesToBase64(self.rsa_key.hashAndSign(data))
+    header = {'Authorization': '%s"%s" data="%s" sig="%s" sigalg="rsa-sha1"' %
+              (AUTHSUB_AUTH_LABEL, self.token_string, data, sig)}
+    return header
+  
+  def perform_request(self, http_client, operation, url, data=None, 
+                      headers=None):
+    """Sets the Authorization header and makes the HTTP request."""
+    if not headers:
+      headers = {}
+    headers.update(self.GetAuthHeader(operation, url))
+    return http_client.request(operation, url, data=data, headers=headers)

Modified: trunk/conduit/modules/GoogleModule/gdata/base/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/base/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/base/service.py	Tue Mar 17 09:00:35 2009
@@ -49,16 +49,28 @@
 class GBaseService(gdata.service.GDataService):
   """Client for the Google Base service."""
 
-  def __init__(self, email=None, password=None, source=None, 
-               server='base.google.com', api_key=None, 
-               additional_headers=None, handler=None):
-    gdata.service.GDataService.__init__(self, email=email, password=password,
-                                        service='gbase', source=source, 
-                                        server=server, 
-                                        additional_headers=additional_headers,
-                                        handler=handler)
+  def __init__(self, email=None, password=None, source=None,
+               server='base.google.com', api_key=None, additional_headers=None,
+               handler=None, **kwargs):
+    """Creates a client for the Google Base service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'base.google.com'.
+      api_key: string (optional) The Google Base API key to use.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='gbase', source=source,
+        server=server, additional_headers=additional_headers, handler=handler,
+        **kwargs)
     self.api_key = api_key
-  
+
   def _SetAPIKey(self, api_key):
     if not isinstance(self.additional_headers, dict):
       self.additional_headers = {}
@@ -178,7 +190,7 @@
       True if the delete succeeded.
     """
     
-    return self.Delete('/%s' % (item_id.lstrip('http://www.google.com/')),
+    return self.Delete('%s' % (item_id[len('http://www.google.com'):],),
                        url_params=url_params, escape_params=escape_params)
                            
   def UpdateItem(self, item_id, updated_item, url_params=None, 

Modified: trunk/conduit/modules/GoogleModule/gdata/blogger/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/blogger/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/blogger/__init__.py	Tue Mar 17 09:00:35 2009
@@ -27,14 +27,16 @@
 
 
 LABEL_SCHEME = 'http://www.blogger.com/atom/ns#'
+THR_NAMESPACE = 'http://purl.org/syndication/thread/1.0'
 
 
 class BloggerEntry(gdata.GDataEntry):
   """Adds convenience methods inherited by all Blogger entries."""
-
+  
   blog_name_pattern = re.compile('(http://)(\w*)')
   blog_id_pattern = re.compile('(tag:blogger.com,1999:blog-)(\w*)')
-
+  blog_id2_pattern = re.compile('tag:blogger.com,1999:user-(\d+)\.blog-(\d+)')
+  
   def GetBlogId(self):
     """Extracts the Blogger id of this blog.
     This method is useful when contructing URLs by hand. The blog id is
@@ -46,7 +48,11 @@
       The blog's unique id as a string.
     """
     if self.id.text:
-      return self.blog_id_pattern.match(self.id.text).group(2)
+      match = self.blog_id_pattern.match(self.id.text)
+      if match:
+        return match.group(2)
+      else:
+        return self.blog_id2_pattern.match(self.id.text).group(2)
     return None
 
   def GetBlogName(self):
@@ -63,33 +69,8 @@
     return None
 
 
-class BlogCommentEntry(BloggerEntry):
-  """Describes a blog comment entry in the feed of a blog's comments.
-
-  """
-  pass
-
-
-def BlogCommentEntryFromString(xml_string):
-  return atom.CreateClassFromXMLString(BlogCommentEntry, xml_string)
-
-
-class BlogCommentFeed(gdata.GDataFeed):
-  """Describes a feed of a blog's comments.
-
-  """
-  pass
-
-
-def BlogCommentFeedFromString(xml_string):
-  return atom.CreateClassFromXMLString(BlogCommentFeed, xml_string)
-
-
 class BlogEntry(BloggerEntry):
-  """Describes a blog entry in the feed of a user's blogs.
-
-  """
-  pass
+  """Describes a blog entry in the feed listing a user's blogs."""
 
 
 def BlogEntryFromString(xml_string):
@@ -97,21 +78,21 @@
 
 
 class BlogFeed(gdata.GDataFeed):
-  """Describes a feed of a user's blogs.
+  """Describes a feed of a user's blogs."""
 
-  """
+  _children = gdata.GDataFeed._children.copy()
+  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BlogEntry])
   
 
-
 def BlogFeedFromString(xml_string):
   return atom.CreateClassFromXMLString(BlogFeed, xml_string)
 
 
 class BlogPostEntry(BloggerEntry):
-  """Describes a blog post entry in the feed of a blog's posts.
-
-  """
+  """Describes a blog post entry in the feed of a blog's posts."""
 
+  post_id_pattern = re.compile('(tag:blogger.com,1999:blog-)(\w*)(.post-)(\w*)')
+  
   def AddLabel(self, label):
     """Adds a label to the blog post. 
 
@@ -123,52 +104,99 @@
     """
     self.category.append(atom.Category(scheme=LABEL_SCHEME, term=label))
 
+  def GetPostId(self):
+    """Extracts the postID string from the entry's Atom id.
+    
+    Returns: A string of digits which identify this post within the blog.
+    """
+    if self.id.text:
+      return self.post_id_pattern.match(self.id.text).group(4)
+    return None
+
 
 def BlogPostEntryFromString(xml_string):
   return atom.CreateClassFromXMLString(BlogPostEntry, xml_string)
 
 
 class BlogPostFeed(gdata.GDataFeed):
-  """Describes a feed of a blog's posts.
-
-  """
-  pass
+  """Describes a feed of a blog's posts."""
+  
+  _children = gdata.GDataFeed._children.copy()
+  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BlogPostEntry])
 
 
 def BlogPostFeedFromString(xml_string):
   return atom.CreateClassFromXMLString(BlogPostFeed, xml_string)
 
 
-class BloggerLink(atom.Link):
-  """Extends the base Link class with Blogger extensions.
-
-  """
-  pass
-
-
-def BloggerLinkFromString(xml_string):
-  return atom.CreateClassFromXMLString(BloggerLink, xml_string)
-
-
-class PostCommentEntry(BloggerEntry):
-  """Describes a blog post comment entry in the feed of a blog post's comments.
-
-  """
-  pass
-
+class InReplyTo(atom.AtomBase):
+  _tag = 'in-reply-to'
+  _namespace = THR_NAMESPACE
+  _attributes = atom.AtomBase._attributes.copy()
+  _attributes['href'] = 'href'
+  _attributes['ref'] = 'ref'
+  _attributes['source'] = 'source'
+  _attributes['type'] = 'type'
+
+  def __init__(self, href=None, ref=None, source=None, type=None, 
+      extension_elements=None, extension_attributes=None, text=None):
+    self.href = href
+    self.ref = ref
+    self.source = source
+    self.type = type
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+    self.text = text
+
+
+def InReplyToFromString(xml_string):
+  return atom.CreateClassFromXMLString(InReplyTo, xml_string)
+
+
+class CommentEntry(BloggerEntry):
+  """Describes a blog post comment entry in the feed of a blog post's 
+  comments."""
+
+  _children = BloggerEntry._children.copy()
+  _children['{%s}in-reply-to' % THR_NAMESPACE] = ('in_reply_to', InReplyTo)
+
+  comment_id_pattern = re.compile('.*-(\w*)$')
+
+  def __init__(self, author=None, category=None, content=None, 
+      contributor=None, atom_id=None, link=None, published=None, rights=None,
+      source=None, summary=None, control=None, title=None, updated=None,
+      in_reply_to=None, extension_elements=None, extension_attributes=None, 
+      text=None):
+    BloggerEntry.__init__(self, author=author, category=category, 
+        content=content, contributor=contributor, atom_id=atom_id, link=link,
+        published=published, rights=rights, source=source, summary=summary, 
+        control=control, title=title, updated=updated, 
+        extension_elements=extension_elements, 
+        extension_attributes=extension_attributes, text=text)
+    self.in_reply_to = in_reply_to
+
+  def GetCommentId(self):
+    """Extracts the commentID string from the entry's Atom id.
+    
+    Returns: A string of digits which identify this post within the blog.
+    """
+    if self.id.text:
+      return self.comment_id_pattern.match(self.id.text).group(1)
+    return None
+    
 
-def PostCommentEntryFromString(xml_string):
-  return atom.CreateClassFromXMLString(PostCommentEntry, xml_string)
+def CommentEntryFromString(xml_string):
+  return atom.CreateClassFromXMLString(CommentEntry, xml_string)
 
 
-class PostCommentFeed(gdata.GDataFeed):
-  """Describes a feed of a blog post's comments.
+class CommentFeed(gdata.GDataFeed):
+  """Describes a feed of a blog post's comments."""
 
-  """
-  pass
+  _children = gdata.GDataFeed._children.copy()
+  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CommentEntry])
 
 
-def PostCommentFeedFromString(xml_string):
-  return atom.CreateClassFromXMLString(PostCommentFeed, xml_string)
+def CommentFeedFromString(xml_string):
+  return atom.CreateClassFromXMLString(CommentFeed, xml_string)
 
 

Modified: trunk/conduit/modules/GoogleModule/gdata/blogger/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/blogger/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/blogger/service.py	Tue Mar 17 09:00:35 2009
@@ -25,28 +25,49 @@
 class BloggerService(gdata.service.GDataService):
 
   def __init__(self, email=None, password=None, source=None,
-               server=None, api_key=None,
-               additional_headers=None):
-    gdata.service.GDataService.__init__(self, email=email, password=password,
-                                        service='blogger', source=source,
-                                        server=server,
-                                        additional_headers=additional_headers)
+               server='www.blogger.com', **kwargs):
+    """Creates a client for the Blogger service.
 
-  def GetBlogFeed(self, uri):
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'www.blogger.com'.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='blogger', source=source,
+        server=server, **kwargs)
+
+  def GetBlogFeed(self, uri=None):
+    """Retrieve a list of the blogs to which the current user may manage."""
+    if not uri:
+      uri = '/feeds/default/blogs'
     return self.Get(uri, converter=gdata.blogger.BlogFeedFromString)
 
-  def GetBlogCommentFeed(self, uri):
-    return self.Get(uri, converter=gdata.blogger.BlogCommentFeedFromString)
+  def GetBlogCommentFeed(self, blog_id=None, uri=None):
+    """Retrieve a list of the comments for this blog."""
+    if blog_id:
+      uri = '/feeds/%s/comments/default' % blog_id
+    return self.Get(uri, converter=gdata.blogger.CommentFeedFromString)
 
-  def GetBlogPostFeed(self, uri):
+  def GetBlogPostFeed(self, blog_id=None, uri=None):
+    if blog_id:
+      uri = '/feeds/%s/posts/default' % blog_id
     return self.Get(uri, converter=gdata.blogger.BlogPostFeedFromString)
 
-  def GetPostCommentFeed(self, uri):
-    return self.Get(uri, converter=gdata.blogger.PostCommentFeedFromString)
+  def GetPostCommentFeed(self, blog_id=None, post_id=None, uri=None):
+    """Retrieve a list of the comments for this particular blog post."""
+    if blog_id and post_id:
+      uri = '/feeds/%s/%s/comments/default' % (blog_id, post_id)
+    return self.Get(uri, converter=gdata.blogger.CommentFeedFromString)
 
   def AddPost(self, entry, blog_id=None, uri=None):
     if blog_id:
-      uri = 'http://www.blogger.com/feeds/%s/posts/default' % blog_id
+      uri = '/feeds/%s/posts/default' % blog_id
     return self.Post(entry, uri, 
                      converter=gdata.blogger.BlogPostEntryFromString)
 
@@ -56,7 +77,66 @@
     return self.Put(entry, uri, 
                     converter=gdata.blogger.BlogPostEntryFromString)
 
-  def PostComment(self, comment_entry, blog_id=None, post_id=None, uri=None):
-    # TODO
-    pass
+  def DeletePost(self, entry=None, uri=None):
+    if not uri:
+      uri = entry.GetEditLink().href
+    return self.Delete(uri)
+
+  def AddComment(self, comment_entry, blog_id=None, post_id=None, uri=None):
+    """Adds a new comment to the specified blog post."""
+    if blog_id and post_id:
+      uri = '/feeds/%s/%s/comments/default' % (blog_id, post_id)
+    return self.Post(comment_entry, uri, 
+                     converter=gdata.blogger.CommentEntryFromString)
+
+  def DeleteComment(self, entry=None, uri=None):
+    if not uri:
+      uri = entry.GetEditLink().href
+    return self.Delete(uri)
+    
+
+class BlogQuery(gdata.service.Query):
 
+  def __init__(self, feed=None, params=None, categories=None, blog_id=None):
+    """Constructs a query object for the list of a user's Blogger blogs.
+    
+    Args:
+      feed: str (optional) The beginning of the URL to be queried. If the
+          feed is not set, and there is no blog_id passed in, the default
+          value is used ('/feeds/default/blogs').
+      params: dict (optional)
+      categories: list (optional)
+      blog_id: str (optional)
+    """
+    if not feed and blog_id:
+      feed = '/feeds/default/blogs/%s' % blog_id
+    elif not feed:
+      feed = '/feeds/default/blogs'
+    gdata.service.Query.__init__(self, feed=feed, params=params, 
+        categories=categories)
+
+
+class BlogPostQuery(gdata.service.Query):
+
+  def __init__(self, feed=None, params=None, categories=None, blog_id=None, 
+      post_id=None):
+    if not feed and blog_id and post_id:
+      feed = '/feeds/%s/posts/default/%s' % (blog_id, post_id)
+    elif not feed and blog_id:
+      feed = '/feeds/%s/posts/default' % blog_id
+    gdata.service.Query.__init__(self, feed=feed, params=params,
+        categories=categories)
+
+
+class BlogCommentQuery(gdata.service.Query):
+
+  def __init__(self, feed=None, params=None, categories=None, blog_id=None, 
+      post_id=None, comment_id=None):
+    if not feed and blog_id and comment_id:
+      feed = '/feeds/%s/comments/default/%s' % (blog_id, comment_id)
+    elif not feed and blog_id and post_id:
+      feed = '/feeds/%s/%s/comments/default' % (blog_id, post_id)
+    elif not feed and blog_id:
+      feed = '/feeds/%s/comments/default' % blog_id
+    gdata.service.Query.__init__(self, feed=feed, params=params,
+        categories=categories)

Modified: trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/calendar/__init__.py	Tue Mar 17 09:00:35 2009
@@ -15,10 +15,6 @@
 # limitations under the License.
 
 
-# TODO:
-#   add text=none to all inits
-
-
 """Contains extensions to ElementWrapper objects used with Google Calendar."""
 
 
@@ -171,7 +167,6 @@
   _children = gdata.GDataFeed._children.copy()
   _attributes = gdata.GDataFeed._attributes.copy()
   _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [CalendarListEntry])
-  #_attributes = {}
 
 
 class Scope(atom.AtomBase):
@@ -221,12 +216,10 @@
                         content=content, atom_id=atom_id, link=link, 
                         published=published, title=title, 
                         updated=updated, text=None)
-
     self.scope = scope
     self.role = role
 
 
-
 class CalendarAclFeed(gdata.GDataFeed, gdata.LinkFinder):
   """A Google Calendar ACL feed flavor of an Atom Feed"""
 
@@ -257,23 +250,9 @@
       [CalendarEventCommentEntry])
 
 
-class ExtendedProperty(atom.AtomBase):
-  """The Google Calendar extendedProperty element"""
-
-  _tag = 'extendedProperty'
-  _namespace = gdata.GDATA_NAMESPACE
-  _children = atom.AtomBase._children.copy()
-  _attributes = atom.AtomBase._attributes.copy()
-  _attributes['name'] = 'name'
-  _attributes['value'] = 'value'
-
-  def __init__(self, name=None, value=None, extension_elements=None,
-      extension_attributes=None, text=None):
-    self.name = name 
-    self.value = value
-    self.text = text
-    self.extension_elements = extension_elements or []
-    self.extension_attributes = extension_attributes or {}
+class ExtendedProperty(gdata.ExtendedProperty):
+  """A transparent subclass of gdata.ExtendedProperty added to this module
+  for backwards compatibility."""
 
     
 class Reminder(atom.AtomBase):
@@ -557,17 +536,18 @@
               'http://schemas.google.com/g/2005#message.reply-to' : 'REPLY_TO',
               'http://schemas.google.com/g/2005#message.to' : 'TO' }
   
-  def __init__(self, extension_elements=None, 
-    extension_attributes=None, text=None):
+  def __init__(self, name=None, email=None, attendee_status=None, 
+      attendee_type=None, rel=None, extension_elements=None, 
+      extension_attributes=None, text=None):
     UriEnumElement.__init__(self, 'who', Who.relEnum, attrib_name='rel',
                             extension_elements=extension_elements,
                             extension_attributes=extension_attributes, 
                             text=text)
-    self.name=None
-    self.email=None
-    self.attendee_status=None
-    self.attendee_type=None
-    self.rel=None
+    self.name = name
+    self.email = email
+    self.attendee_status = attendee_status
+    self.attendee_type = attendee_type
+    self.rel = rel
 
 
 class OriginalEvent(atom.AtomBase):
@@ -925,74 +905,3 @@
   
 def CalendarEventCommentFeedFromString(xml_string):
   return atom.CreateClassFromXMLString(CalendarEventCommentFeed, xml_string)
-
-
-# Code to create atom feeds from element trees
-#_CalendarListFeedFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarListFeed, 'feed', atom.ATOM_NAMESPACE)
-#_CalendarListEntryFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarListEntry, 'entry', atom.ATOM_NAMESPACE)
-#_CalendarAclFeedFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarAclFeed, 'feed', atom.ATOM_NAMESPACE)
-#_CalendarAclEntryFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarAclEntry, 'entry', atom.ATOM_NAMESPACE)
-#_CalendarEventFeedFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarEventFeed, 'feed', atom.ATOM_NAMESPACE)
-#_CalendarEventEntryFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarEventEntry, 'entry', atom.ATOM_NAMESPACE)
-#_CalendarEventCommentFeedFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarEventCommentFeed, 'feed', atom.ATOM_NAMESPACE)
-#_CalendarEventCommentEntryFromElementTree = atom._AtomInstanceFromElementTree(
-#    CalendarEventCommentEntry, 'entry', atom.ATOM_NAMESPACE)
-#_WhereFromElementTree = atom._AtomInstanceFromElementTree(
-#    Where, 'where', gdata.GDATA_NAMESPACE)
-#_WhenFromElementTree = atom._AtomInstanceFromElementTree(
-#    When, 'when', gdata.GDATA_NAMESPACE)
-#_WhoFromElementTree = atom._AtomInstanceFromElementTree(
-#    Who, 'who', gdata.GDATA_NAMESPACE)
-#_VisibilityFromElementTree= atom._AtomInstanceFromElementTree(
-#    Visibility, 'visibility', gdata.GDATA_NAMESPACE)
-#_TransparencyFromElementTree = atom._AtomInstanceFromElementTree(
-#    Transparency, 'transparency', gdata.GDATA_NAMESPACE)
-#_CommentsFromElementTree = atom._AtomInstanceFromElementTree(
-#    Comments, 'comments', gdata.GDATA_NAMESPACE)
-#_EventStatusFromElementTree = atom._AtomInstanceFromElementTree(
-#    EventStatus, 'eventStatus', gdata.GDATA_NAMESPACE)
-#_SendEventNotificationsFromElementTree = atom._AtomInstanceFromElementTree(
-#    SendEventNotifications, 'sendEventNotifications', GCAL_NAMESPACE)
-#_QuickAddFromElementTree = atom._AtomInstanceFromElementTree(
-#    QuickAdd, 'quickadd', GCAL_NAMESPACE)
-#_AttendeeStatusFromElementTree = atom._AtomInstanceFromElementTree(
-#    AttendeeStatus, 'attendeeStatus', gdata.GDATA_NAMESPACE)
-#_AttendeeTypeFromElementTree = atom._AtomInstanceFromElementTree(
-#    AttendeeType, 'attendeeType', gdata.GDATA_NAMESPACE)
-#_ExtendedPropertyFromElementTree = atom._AtomInstanceFromElementTree(
-#    ExtendedProperty, 'extendedProperty', gdata.GDATA_NAMESPACE)
-#_RecurrenceFromElementTree = atom._AtomInstanceFromElementTree(
-#    Recurrence, 'recurrence', gdata.GDATA_NAMESPACE)
-#_RecurrenceExceptionFromElementTree = atom._AtomInstanceFromElementTree(
-#    RecurrenceException, 'recurrenceException', gdata.GDATA_NAMESPACE)
-#_OriginalEventFromElementTree = atom._AtomInstanceFromElementTree(
-#    OriginalEvent, 'originalEvent', gdata.GDATA_NAMESPACE)
-#_ColorFromElementTree = atom._AtomInstanceFromElementTree(
-#    Color, 'color', GCAL_NAMESPACE)
-#_HiddenFromElementTree = atom._AtomInstanceFromElementTree(
-#    Hidden, 'hidden', GCAL_NAMESPACE)
-#_SelectedFromElementTree = atom._AtomInstanceFromElementTree(
-#    Selected, 'selected', GCAL_NAMESPACE)
-#_TimezoneFromElementTree = atom._AtomInstanceFromElementTree(
-#    Timezone, 'timezone', GCAL_NAMESPACE)
-#_AccessLevelFromElementTree = atom._AtomInstanceFromElementTree(
-#    AccessLevel, 'accesslevel', GCAL_NAMESPACE)
-#_ReminderFromElementTree = atom._AtomInstanceFromElementTree(
-#    Reminder, 'reminder', gdata.GDATA_NAMESPACE)
-#_ScopeFromElementTree = atom._AtomInstanceFromElementTree(
-#    Scope, 'scope', GACL_NAMESPACE)
-#_RoleFromElementTree = atom._AtomInstanceFromElementTree(
-#    Role, 'role', GACL_NAMESPACE)
-#_WebContentLinkFromElementTree = atom._AtomInstanceFromElementTree(
-#    WebContentLink, 'link', atom.ATOM_NAMESPACE)
-#_WebContentFromElementTree = atom._AtomInstanceFromElementTree(
-#    WebContent, 'webContent', GCAL_NAMESPACE)
-#_WebContentGadgetPrefFromElementTree = atom._AtomInstanceFromElementTree(
-#    WebContentGadgetPref, 'webContentGadgetPref', GCAL_NAMESPACE)

Modified: trunk/conduit/modules/GoogleModule/gdata/calendar/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/calendar/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/calendar/service.py	Tue Mar 17 09:00:35 2009
@@ -51,13 +51,23 @@
 class CalendarService(gdata.service.GDataService):
   """Client for the Google Calendar service."""
 
-  def __init__(self, email=None, password=None, source=None, 
-               server='www.google.com', 
-               additional_headers=None):
-    gdata.service.GDataService.__init__(self, email=email, password=password,
-                                        service='cl', source=source, 
-                                        server=server, 
-                                        additional_headers=additional_headers)
+  def __init__(self, email=None, password=None, source=None,
+               server='www.google.com', additional_headers=None, **kwargs):
+    """Creates a client for the Google Calendar service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'www.google.com'.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='cl', source=source,
+        server=server, additional_headers=additional_headers, **kwargs)
 
   def GetCalendarEventFeed(self, uri='/calendar/feeds/default/private/full'):
     return self.Get(uri, converter=gdata.calendar.CalendarEventFeedFromString)

Added: trunk/conduit/modules/GoogleModule/gdata/client.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/client.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,101 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'j s google com (Jeff Scudder)'
+
+
+import atom.client
+
+
+# Old imports
+import urllib
+import urlparse
+import gdata.auth
+import gdata.service
+import atom.service
+
+
+class GDClient(atom.client.AtomPubClient):
+  pass
+
+
+SCOPE_URL_PARAM_NAME = gdata.service.SCOPE_URL_PARAM_NAME 
+# Maps the service names used in ClientLogin to scope URLs. 
+CLIENT_LOGIN_SCOPES = gdata.service.CLIENT_LOGIN_SCOPES
+
+
+class AuthorizationRequired(gdata.service.Error):
+  pass
+
+
+class GDataClient(gdata.service.GDataService):
+  """This class is deprecated. 
+  
+  All functionality has been migrated to gdata.service.GDataService.
+  """
+  def __init__(self, application_name=None, tokens=None):
+    gdata.service.GDataService.__init__(self, source=application_name, 
+        tokens=tokens)
+
+  def ClientLogin(self, username, password, service_name, source=None, 
+      account_type=None, auth_url=None, login_token=None, login_captcha=None):
+    gdata.service.GDataService.ClientLogin(self, username=username, 
+        password=password, account_type=account_type, service=service_name,
+        auth_service_url=auth_url, source=source, captcha_token=login_token,
+        captcha_response=login_captcha)
+
+  def Get(self, url, parser):
+    """Simplified interface for Get.
+
+    Requires a parser function which takes the server response's body as
+    the only argument.
+
+    Args:
+      url: A string or something that can be converted to a string using str.
+          The URL of the requested resource.
+      parser: A function which takes the HTTP body from the server as it's
+          only result. Common values would include str, 
+          gdata.GDataEntryFromString, and gdata.GDataFeedFromString.
+
+    Returns: The result of calling parser(http_response_body).
+    """
+    return gdata.service.GDataService.Get(self, uri=url, converter=parser)
+
+  def Post(self, data, url, parser, media_source=None):
+    """Streamlined version of Post.
+
+    Requires a parser function which takes the server response's body as
+    the only argument.
+    """
+    return gdata.service.GDataService.Post(self, data=data, uri=url,
+        media_source=media_source, converter=parser)
+
+  def Put(self, data, url, parser, media_source=None):
+    """Streamlined version of Put.
+
+    Requires a parser function which takes the server response's body as
+    the only argument.
+    """
+    return gdata.service.GDataService.Put(self, data=data, uri=url,
+        media_source=media_source, converter=parser)
+
+  def Delete(self, url):
+    return gdata.service.GDataService.Delete(self, uri=url)
+
+
+ExtractToken = gdata.service.ExtractToken
+GenerateAuthSubRequestUrl = gdata.service.GenerateAuthSubRequestUrl    

Modified: trunk/conduit/modules/GoogleModule/gdata/codesearch/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/codesearch/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/codesearch/service.py	Tue Mar 17 09:00:35 2009
@@ -29,31 +29,26 @@
 
 class CodesearchService(gdata.service.GDataService): 
     """Client extension for Google codesearch service"""
-    
-    def __init__(self, email=None, password=None, source=None, 
-            server='www.google.com', additional_headers=None):
-        """Constructor for the CodesearchService.
+
+    def __init__(self, email=None, password=None, source=None,
+                 server='www.google.com',  additional_headers=None, **kwargs):
+        """Creates a client for the Google codesearch service.
 
         Args:
-            email: string (optional) The e-mail address of the account to use for
-                   authentication.
-            password: string (optional) The password of the account to use for
-                      authentication.
-            source: string (optional) The name of the user's application.
-            server: string (optional) The server the feed is hosted on.
-            additional_headers: dict (optional) Any additional HTTP headers to be
-                                transmitted to the service in the form of key-value
-                                pairs.
-        Yields:
-            A CodesearchService object used to communicate with the Google Codesearch
-            service.
+          email: string (optional) The user's email address, used for
+              authentication.
+          password: string (optional) The user's password.
+          source: string (optional) The name of the user's application.
+          server: string (optional) The name of the server to which a connection
+              will be opened. Default value: 'www.google.com'.
+          **kwargs: The other parameters to pass to gdata.service.GDataService
+              constructor.
         """
+        gdata.service.GDataService.__init__(
+            self, email=email, password=password, service='codesearch',
+            source=source, server=server, additional_headers=additional_headers,
+            **kwargs)
 
-        gdata.service.GDataService.__init__(self,
-                email=email, password=password, service='codesearch',
-                source=source,server=server,
-                additional_headers=additional_headers)
-    
     def Query(self, uri, converter=gdata.codesearch.CodesearchFeedFromString):
         """Queries the Codesearch feed and returns the resulting feed of
            entries.

Modified: trunk/conduit/modules/GoogleModule/gdata/contacts/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/contacts/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/contacts/__init__.py	Tue Mar 17 09:00:35 2009
@@ -34,11 +34,40 @@
 IM_YAHOO = 'http://schemas.google.com/g/2005#YAHOO' # Yahoo Messenger protocol
 IM_SKYPE = 'http://schemas.google.com/g/2005#SKYPE' # Skype protocol
 IM_QQ = 'http://schemas.google.com/g/2005#QQ' # QQ protocol
-IM_GOOGLE_TALK = 'http://schemas.google.com/g/2005#GOOGLE_TALK' # Google Talk protocol
+# Google Talk protocol
+IM_GOOGLE_TALK = 'http://schemas.google.com/g/2005#GOOGLE_TALK'
 IM_ICQ = 'http://schemas.google.com/g/2005#ICQ' # ICQ protocol
 IM_JABBER = 'http://schemas.google.com/g/2005#JABBER' # Jabber protocol
 
 
+PHOTO_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#photo'
+PHOTO_EDIT_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#edit-photo'
+
+
+PHONE_CAR = 'http://schemas.google.com/g/2005#car' #  Number of a car phone.
+PHONE_FAX = 'http://schemas.google.com/g/2005#fax'
+# Unknown or unspecified type, such as a business phone number that doesn't
+# belong to a particular person. 
+PHONE_GENERAL = 'http://schemas.google.com/g/2005#general'
+PHONE_HOME = REL_HOME
+PHONE_HOME_FAX = 'http://schemas.google.com/g/2005#home_fax' 
+# Phone number that makes sense only in a context known to the user (such as
+# an enterprise PBX).
+PHONE_INTERNAL = 'http://schemas.google.com/g/2005#internal-extension'
+PHONE_MOBILE = 'http://schemas.google.com/g/2005#mobile' 
+# A special type of number for which no other rel value makes sense. 
+# For example, a TTY device. label can be used to indicate the actual type.
+PHONE_OTHER = REL_OTHER
+PHONE_PAGER = 'http://schemas.google.com/g/2005#pager'
+PHONE_SATELLITE = 'http://schemas.google.com/g/2005#satellite'
+PHONE_VOIP = 'http://schemas.google.com/g/2005#voip'
+PHONE_WORK = REL_WORK
+PHONE_WORK_FAX = 'http://schemas.google.com/g/2005#work_fax'
+
+
+CONTACTS_NAMESPACE = 'http://schemas.google.com/contact/2008'
+
+
 class OrgName(atom.AtomBase):
   _tag = 'orgName'
   _namespace = gdata.GDATA_NAMESPACE
@@ -51,6 +80,7 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
 class OrgTitle(atom.AtomBase):
   _tag = 'orgTitle'
   _namespace = gdata.GDATA_NAMESPACE
@@ -63,6 +93,7 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
 class Organization(atom.AtomBase):
   _tag = 'organization'
   _namespace = gdata.GDATA_NAMESPACE
@@ -88,6 +119,7 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
 class PostalAddress(atom.AtomBase):
   _tag = 'postalAddress'
   _namespace = gdata.GDATA_NAMESPACE
@@ -105,6 +137,7 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
 class IM(atom.AtomBase):
   _tag = 'im'
   _namespace = gdata.GDATA_NAMESPACE
@@ -129,6 +162,7 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
 class Email(atom.AtomBase):
   _tag = 'email'
   _namespace = gdata.GDATA_NAMESPACE
@@ -150,6 +184,7 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
 class PhoneNumber(atom.AtomBase):
   _tag = 'phoneNumber'
   _namespace = gdata.GDATA_NAMESPACE
@@ -167,6 +202,7 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
 class Deleted(atom.AtomBase):
   _tag = 'deleted'
   _namespace = gdata.GDATA_NAMESPACE
@@ -177,41 +213,54 @@
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+
+class GroupMembershipInfo(atom.AtomBase):
+  _tag = 'groupMembershipInfo'
+  _namespace = CONTACTS_NAMESPACE
+  _attributes = atom.AtomBase._attributes.copy()
+
+  _attributes['deleted'] = 'deleted'
+  _attributes['href'] = 'href'
+
+  def __init__(self, deleted=None, href=None, text=None,
+      extension_elements=None, extension_attributes=None):
+    self.deleted = deleted
+    self.href = href
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
 class ContactEntry(gdata.BatchEntry):
   """A Google Contact flavor of an Atom Entry """
 
-  _tag = gdata.BatchEntry._tag
-  _namespace = gdata.BatchEntry._namespace
   _children = gdata.BatchEntry._children.copy()
-  _attributes = gdata.BatchEntry._attributes.copy()
 
-  _children['{%s}postalAddress' % gdata.GDATA_NAMESPACE] = ('postal_address', [PostalAddress])
-  _children['{%s}phoneNumber' % gdata.GDATA_NAMESPACE] = ('phone_number', [PhoneNumber])
-  _children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization', Organization)
+  _children['{%s}postalAddress' % gdata.GDATA_NAMESPACE] = ('postal_address',
+      [PostalAddress])
+  _children['{%s}phoneNumber' % gdata.GDATA_NAMESPACE] = ('phone_number',
+      [PhoneNumber])
+  _children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization',
+      Organization)
   _children['{%s}email' % gdata.GDATA_NAMESPACE] = ('email', [Email])
   _children['{%s}im' % gdata.GDATA_NAMESPACE] = ('im', [IM])
   _children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted)
+  _children['{%s}groupMembershipInfo' % CONTACTS_NAMESPACE] = (
+      'group_membership_info', [GroupMembershipInfo])
+  _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = (
+      'extended_property', [gdata.ExtendedProperty])
   
   def __init__(self, author=None, category=None, content=None,
       atom_id=None, link=None, published=None, 
-      title=None, updated=None, 
-      transparency=None, comments=None, email=None,
-      postal_address=None, deleted=None,
-      organization=None, phone_number=None, im=None,
-      extended_property=None, original_event=None,
+      title=None, updated=None, email=None, postal_address=None, 
+      deleted=None, organization=None, phone_number=None, im=None,
+      extended_property=None, group_membership_info=None,
       batch_operation=None, batch_id=None, batch_status=None,
       extension_elements=None, extension_attributes=None, text=None):
-
-
     gdata.BatchEntry.__init__(self, author=author, category=category, 
-                        content=content,
-                        atom_id=atom_id, link=link, published=published,
-                        batch_operation=batch_operation, batch_id=batch_id, 
-                        batch_status=batch_status,
-                        title=title, updated=updated)
-    
-    self.transparency = transparency 
-    self.comments = comments
+        content=content, atom_id=atom_id, link=link, published=published,
+        batch_operation=batch_operation, batch_id=batch_id, 
+        batch_status=batch_status, title=title, updated=updated)
     self.organization = organization
     self.deleted = deleted
     self.phone_number = phone_number or []
@@ -219,20 +268,32 @@
     self.im = im or []  
     self.extended_property = extended_property or []
     self.email = email or []
+    self.group_membership_info = group_membership_info or []
     self.text = text
     self.extension_elements = extension_elements or []
     self.extension_attributes = extension_attributes or {}
 
+  def GetPhotoLink(self):
+    for a_link in self.link:
+      if a_link.rel == PHOTO_LINK_REL:
+        return a_link
+    return None
+
+  def GetPhotoEditLink(self):
+    for a_link in self.link:
+      if a_link.rel == PHOTO_EDIT_LINK_REL:
+        return a_link
+    return None
+
+
 def ContactEntryFromString(xml_string):
   return atom.CreateClassFromXMLString(ContactEntry, xml_string)
 
-class ContactsFeed(gdata.GDataFeed, gdata.LinkFinder):
+
+class ContactsFeed(gdata.BatchFeed, gdata.LinkFinder):
   """A Google Contacts feed flavor of an Atom Feed"""
 
-  _tag = gdata.GDataFeed._tag
-  _namespace = gdata.GDataFeed._namespace
-  _children = gdata.GDataFeed._children.copy()
-  _attributes = gdata.GDataFeed._attributes.copy()
+  _children = gdata.BatchFeed._children.copy()
 
   _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ContactEntry])
 
@@ -242,7 +303,7 @@
                entry=None, total_results=None, start_index=None,
                items_per_page=None, extension_elements=None,
                extension_attributes=None, text=None):
-    gdata.GDataFeed.__init__(self, author=author, category=category,
+    gdata.BatchFeed.__init__(self, author=author, category=category,
                              contributor=contributor, generator=generator,
                              icon=icon,  atom_id=atom_id, link=link,
                              logo=logo, rights=rights, subtitle=subtitle,
@@ -254,5 +315,41 @@
                              extension_attributes=extension_attributes,
                              text=text)
                              
+
 def ContactsFeedFromString(xml_string):
   return atom.CreateClassFromXMLString(ContactsFeed, xml_string)
+
+
+class GroupEntry(gdata.BatchEntry):
+  """Represents a contact group."""
+  _children = gdata.BatchEntry._children.copy()
+  _children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = (
+      'extended_property', [gdata.ExtendedProperty])
+
+  def __init__(self, author=None, category=None, content=None,
+      contributor=None, atom_id=None, link=None, published=None, rights=None,
+      source=None, summary=None, control=None, title=None, updated=None,
+      extended_property=None, batch_operation=None, batch_id=None, 
+      batch_status=None, 
+      extension_elements=None, extension_attributes=None, text=None):
+    gdata.BatchEntry.__init__(self, author=author, category=category, 
+                        content=content,
+                        atom_id=atom_id, link=link, published=published,
+                        batch_operation=batch_operation, batch_id=batch_id, 
+                        batch_status=batch_status,
+                        title=title, updated=updated)
+    self.extended_property = extended_property or []
+
+
+def GroupEntryFromString(xml_string):
+  return atom.CreateClassFromXMLString(GroupEntry, xml_string)
+
+
+class GroupsFeed(gdata.BatchFeed):
+  """A Google contact groups feed flavor of an Atom Feed"""
+  _children = gdata.BatchFeed._children.copy()
+  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GroupEntry])
+
+
+def GroupsFeedFromString(xml_string):
+  return atom.CreateClassFromXMLString(GroupsFeed, xml_string)

Modified: trunk/conduit/modules/GoogleModule/gdata/contacts/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/contacts/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/contacts/service.py	Tue Mar 17 09:00:35 2009
@@ -33,6 +33,9 @@
 import gdata.calendar
 import atom
 
+DEFAULT_BATCH_URL = ('http://www.google.com/m8/feeds/contacts/default/full'
+                     '/batch')
+
 class Error(Exception):
   pass
 
@@ -40,27 +43,66 @@
   pass
 
 class ContactsService(gdata.service.GDataService):
-  """Client for the Google Contats service."""
+  """Client for the Google Contacts service."""
+
+  def __init__(self, email=None, password=None, source=None,
+               server='www.google.com', additional_headers=None,
+               contact_list='default', **kwargs):
+    """Creates a client for the Contacts service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'www.google.com'.
+      contact_list: string (optional) The name of the default contact list to
+          use when no URI is specified to the methods of the service.
+          Default value: 'default' (the logged in user's contact list).
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    self.contact_list = contact_list
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='cp', source=source,
+        server=server, additional_headers=additional_headers, **kwargs)
+
+  def GetFeedUri(self, kind='contacts', contact_list=None, projection='full',
+                 scheme=None):
+    """Builds a feed URI.
+
+    Args:
+      kind: The type of feed to return, typically 'groups' or 'contacts'.
+        Default value: 'contacts'.
+      contact_list: The contact list to return a feed for.
+        Default value: self.contact_list.
+      projection: The projection to apply to the feed contents, for example
+        'full', 'base', 'base/12345', 'full/batch'. Default value: 'full'.
+      scheme: The URL scheme such as 'http' or 'https', None to return a
+          relative URI without hostname.
 
-  def __init__(self, email=None, password=None, source=None, 
-               server='www.google.com', 
-               additional_headers=None):
-    gdata.service.GDataService.__init__(self, email=email, password=password,
-                                        service='cp', source=source, 
-                                        server=server, 
-                                        additional_headers=additional_headers)
+    Returns:
+      A feed URI using the given kind, contact list, and projection.
+      Example: '/m8/feeds/contacts/default/full'.
+    """
+    contact_list = contact_list or self.contact_list
+    prefix = scheme and '%s://%s' % (scheme, self.server) or ''
+    return '%s/m8/feeds/%s/%s/%s' % (prefix, kind, contact_list, projection)
 
-  def GetContactsFeed(self, 
-      uri='http://www.google.com/m8/feeds/contacts/default/base'):
+  def GetContactsFeed(self, uri=None):
+    uri = uri or self.GetFeedUri()
     return self.Get(uri, converter=gdata.contacts.ContactsFeedFromString)
 
-  def CreateContact(self, new_contact, 
-      insert_uri='/m8/feeds/contacts/default/base', url_params=None, 
-      escape_params=True):
-    """Adds an event to Google Contacts.
+  def GetContact(self, uri):
+    return self.Get(uri, converter=gdata.contacts.ContactEntryFromString)
+
+  def CreateContact(self, new_contact, insert_uri=None, url_params=None,
+                    escape_params=True):
+    """Adds an new contact to Google Contacts.
 
     Args: 
-      new_contact: atom.Entry or subclass A new event which is to be added to
+      new_contact: atom.Entry or subclass A new contact which is to be added to
                 Google Contacts.
       insert_uri: the URL to post new contacts to the feed
       url_params: dict (optional) Additional URL parameters to be included
@@ -75,6 +117,7 @@
          'reason': HTTP reason from the server, 
          'body': HTTP body of the server's response}
     """
+    insert_uri = insert_uri or self.GetFeedUri()
     return self.Post(new_contact, insert_uri, url_params=url_params,
         escape_params=escape_params,
         converter=gdata.contacts.ContactEntryFromString)
@@ -87,7 +130,7 @@
     Args:
       edit_uri: string The edit link URI for the element being updated
       updated_contact: string, atom.Entry or subclass containing
-                    the Atom Entry which will replace the event which is 
+                    the Atom Entry which will replace the contact which is 
                     stored at the edit_url 
       url_params: dict (optional) Additional URL parameters to be included
                   in the update request.
@@ -102,24 +145,18 @@
          'reason': HTTP reason from the server, 
          'body': HTTP body of the server's response}
     """
-    url_prefix = 'http://%s/' % self.server
-    if edit_uri.startswith(url_prefix):
-      edit_uri = edit_uri[len(url_prefix):]
-    response = self.Put(updated_contact, '/%s' % edit_uri,
-                        url_params=url_params, 
-                        escape_params=escape_params)
-    if isinstance(response, atom.Entry):
-      return gdata.contacts.ContactEntryFromString(response.ToString())
-    else:
-      return response
+    return self.Put(updated_contact, self._CleanUri(edit_uri),
+                    url_params=url_params,
+                    escape_params=escape_params,
+                    converter=gdata.contacts.ContactEntryFromString)
 
   def DeleteContact(self, edit_uri, extra_headers=None, 
       url_params=None, escape_params=True):
-    """Removes an event with the specified ID from Google Contacts.
+    """Removes an contact with the specified ID from Google Contacts.
 
     Args:
       edit_uri: string The edit URL of the entry to be deleted. Example:
-               'http://www.google.com/m8/feeds/contacts/default/base/xxx/yyy'
+               '/m8/feeds/contacts/default/full/xxx/yyy'
       url_params: dict (optional) Additional URL parameters to be included
                   in the deletion request.
       escape_params: boolean (optional) If true, the url_parameters will be
@@ -133,18 +170,166 @@
          'reason': HTTP reason from the server, 
          'body': HTTP body of the server's response}
     """
-    
-    url_prefix = 'http://%s/' % self.server
-    if edit_uri.startswith(url_prefix):
-      edit_uri = edit_uri[len(url_prefix):]
-    return self.Delete('/%s' % edit_uri,
+    return self.Delete(self._CleanUri(edit_uri),
                        url_params=url_params, escape_params=escape_params)
 
+  def GetGroupsFeed(self, uri=None):
+    uri = uri or self.GetFeedUri('groups')
+    return self.Get(uri, converter=gdata.contacts.GroupsFeedFromString)
+
+  def CreateGroup(self, new_group, insert_uri=None, url_params=None,
+                  escape_params=True):
+    insert_uri = insert_uri or self.GetFeedUri('groups')
+    return self.Post(new_group, insert_uri, url_params=url_params,
+        escape_params=escape_params,
+        converter=gdata.contacts.GroupEntryFromString)
+
+  def UpdateGroup(self, edit_uri, updated_group, url_params=None, 
+                  escape_params=True):
+    return self.Put(updated_group, self._CleanUri(edit_uri),
+                    url_params=url_params,
+                    escape_params=escape_params,
+                    converter=gdata.contacts.GroupEntryFromString)
+
+  def DeleteGroup(self, edit_uri, extra_headers=None, 
+      url_params=None, escape_params=True):
+    return self.Delete(self._CleanUri(edit_uri),
+                       url_params=url_params, escape_params=escape_params)
+
+  def ChangePhoto(self, media, contact_entry_or_url, content_type=None, 
+      content_length=None):
+    """Change the photo for the contact by uploading a new photo.
+
+    Performs a PUT against the photo edit URL to send the binary data for the
+    photo.
+
+    Args:
+      media: filename, file-like-object, or a gdata.MediaSource object to send.
+      contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this
+                            method will search for an edit photo link URL and
+                            perform a PUT to the URL.
+      content_type: str (optional) the mime type for the photo data. This is
+                    necessary if media is a file or file name, but if media
+                    is a MediaSource object then the media object can contain
+                    the mime type. If media_type is set, it will override the
+                    mime type in the media object.
+      content_length: int or str (optional) Specifying the content length is
+                      only required if media is a file-like object. If media
+                      is a filename, the length is determined using 
+                      os.path.getsize. If media is a MediaSource object, it is
+                      assumed that it already contains the content length.
+    """
+    if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry):
+      url = contact_entry_or_url.GetPhotoEditLink().href
+    else:
+      url = contact_entry_or_url
+    if isinstance(media, gdata.MediaSource):
+      payload = media
+    # If the media object is a file-like object, then use it as the file
+    # handle in the in the MediaSource.
+    elif hasattr(media, 'read'):
+      payload = gdata.MediaSource(file_handle=media, 
+          content_type=content_type, content_length=content_length)
+    # Assume that the media object is a file name.
+    else:
+      payload = gdata.MediaSource(content_type=content_type, 
+          content_length=content_length, file_path=media)
+    return self.Put(payload, url)
+
+  def GetPhoto(self, contact_entry_or_url):
+    """Retrives the binary data for the contact's profile photo as a string.
+    
+    Args:
+      contact_entry_or_url: a gdata.contacts.ContactEntry objecr or a string
+         containing the photo link's URL. If the contact entry does not 
+         contain a photo link, the image will not be fetched and this method
+         will return None.
+    """
+    # TODO: add the ability to write out the binary image data to a file, 
+    # reading and writing a chunk at a time to avoid potentially using up 
+    # large amounts of memory.
+    url = None
+    if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry):
+      photo_link = contact_entry_or_url.GetPhotoLink()
+      if photo_link:
+        url = photo_link.href
+    else:
+      url = contact_entry_or_url
+    if url:
+      return self.Get(url, converter=str)
+    else:
+      return None
+
+  def DeletePhoto(self, contact_entry_or_url):
+    url = None
+    if isinstance(contact_entry_or_url, gdata.contacts.ContactEntry):
+      url = contact_entry_or_url.GetPhotoEditLink().href
+    else:
+      url = contact_entry_or_url
+    if url:
+      self.Delete(url)
+
+  def ExecuteBatch(self, batch_feed, url,
+                   converter=gdata.contacts.ContactsFeedFromString):
+    """Sends a batch request feed to the server.
+    
+    Args:
+      batch_feed: gdata.contacts.ContactFeed A feed containing batch
+          request entries. Each entry contains the operation to be performed
+          on the data contained in the entry. For example an entry with an
+          operation type of insert will be used as if the individual entry
+          had been inserted.
+      url: str The batch URL to which these operations should be applied.
+      converter: Function (optional) The function used to convert the server's
+          response to an object. The default value is ContactsFeedFromString.
+    
+    Returns:
+      The results of the batch request's execution on the server. If the
+      default converter is used, this is stored in a ContactsFeed.
+    """
+    return self.Post(batch_feed, url, converter=converter)
+
+  def _CleanUri(self, uri):
+    """Sanitizes a feed URI.
+
+    Args:
+      uri: The URI to sanitize, can be relative or absolute.
+
+    Returns:
+      The given URI without its http://server prefix, if any.
+      Keeps the leading slash of the URI.
+    """
+    url_prefix = 'http://%s' % self.server
+    if uri.startswith(url_prefix):
+      uri = uri[len(url_prefix):]
+    return uri
 
 class ContactsQuery(gdata.service.Query):
 
   def __init__(self, feed=None, text_query=None, params=None,
+      categories=None, group=None):
+    self.feed = feed or '/m8/feeds/contacts/default/full'
+    if group:
+      self._SetGroup(group)
+    gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query,
+        params=params, categories=categories)
+
+  def _GetGroup(self):
+    if 'group' in self:
+      return self['group']
+    else:
+      return None
+
+  def _SetGroup(self, group_id):
+    self['group'] = group_id
+
+  group = property(_GetGroup, _SetGroup, 
+      doc='The group query parameter to find only contacts in this group')
+
+class GroupsQuery(gdata.service.Query):
+
+  def __init__(self, feed=None, text_query=None, params=None,
       categories=None):
-    self.feed = feed or '/m8/feeds/contacts/default/base'
+    self.feed = feed or '/m8/feeds/groups/default/full'
     gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query,
         params=params, categories=categories)

Modified: trunk/conduit/modules/GoogleModule/gdata/docs/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/docs/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/docs/service.py	Tue Mar 17 09:00:35 2009
@@ -61,28 +61,22 @@
   """Client extension for the Google Documents service Document List feed."""
 
   def __init__(self, email=None, password=None, source=None,
-      server='docs.google.com', additional_headers=None):
-    """Constructor for the DocsService.
+               server='docs.google.com', additional_headers=None, **kwargs):
+    """Creates a client for the Google Documents service.
 
     Args:
-      email: string (optional) The e-mail address of the account to use for
-             authentication.
-      password: string (optional) The password of the account to use for
-                authentication.
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
       source: string (optional) The name of the user's application.
-      server: string (optional) The server the feed is hosted on.
-      additional_headers: dict (optional) Any additional HTTP headers to be
-                          transmitted to the service in the form of key-value
-                          pairs.
-
-    Yields:
-      A DocsService object used to communicate with the Google Documents
-      service.
-    """
-    gdata.service.GDataService.__init__(self, email=email, password=password,
-                                        service='writely', source=source,
-                                        server=server,
-                                        additional_headers=additional_headers)
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'docs.google.com'.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='writely', source=source,
+        server=server, additional_headers=additional_headers, **kwargs)
 
   def Query(self, uri, converter=gdata.docs.DocumentListFeedFromString):
     """Queries the Document List feed and returns the resulting feed of

Modified: trunk/conduit/modules/GoogleModule/gdata/geo/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/geo/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/geo/__init__.py	Tue Mar 17 09:00:35 2009
@@ -106,7 +106,7 @@
   As a convenience, you can get a tuple of (lat, lon) with Where.location(),
   and set the same data with Where.setLocation( (lat, lon) ).
 
-  Similarly, there are methods to set and get only latitude and longtitude.
+  Similarly, there are methods to set and get only latitude and longitude.
   """
   
   _tag = 'where'
@@ -149,11 +149,13 @@
     lat, lon = self.location()
     return lat
   
-  def longtitude(self):
+  def longitude(self):
     "(float) Get the longtitude value of the geo-tag. See also .location()"
     lat, lon = self.location()
     return lon
 
+  longtitude = longitude
+
   def set_latitude(self, lat):
     """(bool) Set the latitude value of the geo-tag.
 
@@ -165,7 +167,7 @@
     _lat, lon = self.location()
     return self.set_location(lat, lon)
   
-  def set_longtitude(self, lon):
+  def set_longitude(self, lon):
     """(bool) Set the longtitude value of the geo-tag.
     
     Args:
@@ -176,6 +178,8 @@
     lat, _lon = self.location()
     return self.set_location(lat, lon)
 
+  set_longtitude = set_longitude
+
 def WhereFromString(xml_string):
   return atom.CreateClassFromXMLString(Where, xml_string)
   

Modified: trunk/conduit/modules/GoogleModule/gdata/media/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/media/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/media/__init__.py	Tue Mar 17 09:00:35 2009
@@ -206,6 +206,8 @@
     self.url = url
     self.width = width
     self.height = height
+
+
 def ThumbnailFromString(xml_string):
   return atom.CreateClassFromXMLString(Thumbnail, xml_string)
 
@@ -301,7 +303,7 @@
   _children['{%s}keywords' % MEDIA_NAMESPACE] = ('keywords', Keywords) 
   _children['{%s}thumbnail' % MEDIA_NAMESPACE] = ('thumbnail', [Thumbnail,])
   _children['{%s}title' % MEDIA_NAMESPACE] = ('title', Title) 
-  _children['{%s}category' % MEDIA_NAMESPACE] = ('category', Category) 
+  _children['{%s}category' % MEDIA_NAMESPACE] = ('category', [Category,]) 
   _children['{%s}duration' % YOUTUBE_NAMESPACE] = ('duration', Duration)
   _children['{%s}private' % YOUTUBE_NAMESPACE] = ('private', Private)
   _children['{%s}player' % MEDIA_NAMESPACE] = ('player', Player)
@@ -322,7 +324,7 @@
     self.title=title
     self.duration=duration
     self.private=private
-    self.category=category
+    self.category=category or []
     self.player=player
 
 def GroupFromString(xml_string):

Added: trunk/conduit/modules/GoogleModule/gdata/oauth/CHANGES.txt
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/oauth/CHANGES.txt	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,17 @@
+1. Moved oauth.py to __init__.py
+
+2. Refactored __init__.py for compatibility with python 2.2 (Issue 59)
+
+3. Refactored rsa.py for compatibility with python 2.2 (Issue 59)
+
+4. Refactored OAuthRequest.from_token_and_callback since the callback url was
+getting double url-encoding the callback url in place of single. (Issue 43)
+
+5. Added build_signature_base_string method to rsa.py since it used the
+implementation of this method from oauth.OAuthSignatureMethod_HMAC_SHA1 which
+was incorrect since it enforced the presence of a consumer secret and a token
+secret. Also, changed its super class from oauth.OAuthSignatureMethod_HMAC_SHA1
+to oauth.OAuthSignatureMethod (Issue 64)
+
+6. Refactored <OAuthRequest>.to_header method since it returned non-oauth params
+as well which was incorrect. (Issue 31)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/oauth/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/oauth/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,524 @@
+import cgi
+import urllib
+import time
+import random
+import urlparse
+import hmac
+import binascii
+
+VERSION = '1.0' # Hi Blaine!
+HTTP_METHOD = 'GET'
+SIGNATURE_METHOD = 'PLAINTEXT'
+
+# Generic exception class
+class OAuthError(RuntimeError):
+    def __init__(self, message='OAuth error occured.'):
+        self.message = message
+
+# optional WWW-Authenticate header (401 error)
+def build_authenticate_header(realm=''):
+    return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+# url escape
+def escape(s):
+    # escape '/' too
+    return urllib.quote(s, safe='~')
+
+# util function: current timestamp
+# seconds since epoch (UTC)
+def generate_timestamp():
+    return int(time.time())
+
+# util function: nonce
+# pseudorandom number
+def generate_nonce(length=8):
+    return ''.join([str(random.randint(0, 9)) for i in range(length)])
+
+# OAuthConsumer is a data type that represents the identity of the Consumer
+# via its shared secret with the Service Provider.
+class OAuthConsumer(object):
+    key = None
+    secret = None
+
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+# OAuthToken is a data type that represents an End User via either an access
+# or request token.     
+class OAuthToken(object):
+    # access tokens and request tokens
+    key = None
+    secret = None
+
+    '''
+    key = the token
+    secret = the token secret
+    '''
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+    def to_string(self):
+        return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret})
+
+    # return a token from something like:
+    # oauth_token_secret=digg&oauth_token=digg
+    def from_string(s):
+        params = cgi.parse_qs(s, keep_blank_values=False)
+        key = params['oauth_token'][0]
+        secret = params['oauth_token_secret'][0]
+        return OAuthToken(key, secret)
+    from_string = staticmethod(from_string)
+
+    def __str__(self):
+        return self.to_string()
+
+# OAuthRequest represents the request and can be serialized
+class OAuthRequest(object):
+    '''
+    OAuth parameters:
+        - oauth_consumer_key 
+        - oauth_token
+        - oauth_signature_method
+        - oauth_signature 
+        - oauth_timestamp 
+        - oauth_nonce
+        - oauth_version
+        ... any additional parameters, as defined by the Service Provider.
+    '''
+    parameters = None # oauth parameters
+    http_method = HTTP_METHOD
+    http_url = None
+    version = VERSION
+
+    def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        self.http_method = http_method
+        self.http_url = http_url
+        self.parameters = parameters or {}
+
+    def set_parameter(self, parameter, value):
+        self.parameters[parameter] = value
+
+    def get_parameter(self, parameter):
+        try:
+            return self.parameters[parameter]
+        except:
+            raise OAuthError('Parameter not found: %s' % parameter)
+
+    def _get_timestamp_nonce(self):
+        return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce')
+
+    # get any non-oauth parameters
+    def get_nonoauth_parameters(self):
+        parameters = {}
+        for k, v in self.parameters.iteritems():
+            # ignore oauth parameters
+            if k.find('oauth_') < 0:
+                parameters[k] = v
+        return parameters
+
+    # serialize as a header for an HTTPAuth request
+    def to_header(self, realm=''):
+        auth_header = 'OAuth realm="%s"' % realm
+        # add the oauth parameters
+        if self.parameters:
+            for k, v in self.parameters.iteritems():
+                if k[:6] == 'oauth_':
+                    auth_header += ', %s="%s"' % (k, escape(str(v)))
+        return {'Authorization': auth_header}
+
+    # serialize as post data for a POST request
+    def to_postdata(self):
+        return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()])
+
+    # serialize as a url for a GET request
+    def to_url(self):
+        return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
+
+    # return a string that consists of all the parameters that need to be signed
+    def get_normalized_parameters(self):
+        params = self.parameters
+        try:
+            # exclude the signature if it exists
+            del params['oauth_signature']
+        except:
+            pass
+        key_values = params.items()
+        # sort lexicographically, first after key, then after value
+        key_values.sort()
+        # combine key value pairs in string and escape
+        return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values])
+
+    # just uppercases the http method
+    def get_normalized_http_method(self):
+        return self.http_method.upper()
+
+    # parses the url and rebuilds it to be scheme://host/path
+    def get_normalized_http_url(self):
+        parts = urlparse.urlparse(self.http_url)
+        url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path
+        return url_string
+        
+    # set the signature parameter to the result of build_signature
+    def sign_request(self, signature_method, consumer, token):
+        # set the signature method
+        self.set_parameter('oauth_signature_method', signature_method.get_name())
+        # set the signature
+        self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token))
+
+    def build_signature(self, signature_method, consumer, token):
+        # call the build signature method within the signature method
+        return signature_method.build_signature(self, consumer, token)
+
+    def from_request(http_method, http_url, headers=None, parameters=None, query_string=None):
+        # combine multiple parameter sources
+        if parameters is None:
+            parameters = {}
+
+        # headers
+        if headers and 'Authorization' in headers:
+            auth_header = headers['Authorization']
+            # check that the authorization header is OAuth
+            if auth_header.index('OAuth') > -1:
+                try:
+                    # get the parameters from the header
+                    header_params = OAuthRequest._split_header(auth_header)
+                    parameters.update(header_params)
+                except:
+                    raise OAuthError('Unable to parse OAuth parameters from Authorization header.')
+
+        # GET or POST query string
+        if query_string:
+            query_params = OAuthRequest._split_url_string(query_string)
+            parameters.update(query_params)
+
+        # URL parameters
+        param_str = urlparse.urlparse(http_url)[4] # query
+        url_params = OAuthRequest._split_url_string(param_str)
+        parameters.update(url_params)
+
+        if parameters:
+            return OAuthRequest(http_method, http_url, parameters)
+
+        return None
+    from_request = staticmethod(from_request)
+
+    def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        if not parameters:
+            parameters = {}
+
+        defaults = {
+            'oauth_consumer_key': oauth_consumer.key,
+            'oauth_timestamp': generate_timestamp(),
+            'oauth_nonce': generate_nonce(),
+            'oauth_version': OAuthRequest.version,
+        }
+
+        defaults.update(parameters)
+        parameters = defaults
+
+        if token:
+            parameters['oauth_token'] = token.key
+
+        return OAuthRequest(http_method, http_url, parameters)
+    from_consumer_and_token = staticmethod(from_consumer_and_token)
+
+    def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        if not parameters:
+            parameters = {}
+
+        parameters['oauth_token'] = token.key
+
+        if callback:
+            parameters['oauth_callback'] = callback
+
+        return OAuthRequest(http_method, http_url, parameters)
+    from_token_and_callback = staticmethod(from_token_and_callback)
+
+    # util function: turn Authorization: header into parameters, has to do some unescaping
+    def _split_header(header):
+        params = {}
+        parts = header.split(',')
+        for param in parts:
+            # ignore realm parameter
+            if param.find('OAuth realm') > -1:
+                continue
+            # remove whitespace
+            param = param.strip()
+            # split key-value
+            param_parts = param.split('=', 1)
+            # remove quotes and unescape the value
+            params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
+        return params
+    _split_header = staticmethod(_split_header)
+    
+    # util function: turn url string into parameters, has to do some unescaping
+    def _split_url_string(param_str):
+        parameters = cgi.parse_qs(param_str, keep_blank_values=False)
+        for k, v in parameters.iteritems():
+            parameters[k] = urllib.unquote(v[0])
+        return parameters
+    _split_url_string = staticmethod(_split_url_string)
+
+# OAuthServer is a worker to check a requests validity against a data store
+class OAuthServer(object):
+    timestamp_threshold = 300 # in seconds, five minutes
+    version = VERSION
+    signature_methods = None
+    data_store = None
+
+    def __init__(self, data_store=None, signature_methods=None):
+        self.data_store = data_store
+        self.signature_methods = signature_methods or {}
+
+    def set_data_store(self, oauth_data_store):
+        self.data_store = data_store
+
+    def get_data_store(self):
+        return self.data_store
+
+    def add_signature_method(self, signature_method):
+        self.signature_methods[signature_method.get_name()] = signature_method
+        return self.signature_methods
+
+    # process a request_token request
+    # returns the request token on success
+    def fetch_request_token(self, oauth_request):
+        try:
+            # get the request token for authorization
+            token = self._get_token(oauth_request, 'request')
+        except OAuthError:
+            # no token required for the initial token request
+            version = self._get_version(oauth_request)
+            consumer = self._get_consumer(oauth_request)
+            self._check_signature(oauth_request, consumer, None)
+            # fetch a new token
+            token = self.data_store.fetch_request_token(consumer)
+        return token
+
+    # process an access_token request
+    # returns the access token on success
+    def fetch_access_token(self, oauth_request):
+        version = self._get_version(oauth_request)
+        consumer = self._get_consumer(oauth_request)
+        # get the request token
+        token = self._get_token(oauth_request, 'request')
+        self._check_signature(oauth_request, consumer, token)
+        new_token = self.data_store.fetch_access_token(consumer, token)
+        return new_token
+
+    # verify an api call, checks all the parameters
+    def verify_request(self, oauth_request):
+        # -> consumer and token
+        version = self._get_version(oauth_request)
+        consumer = self._get_consumer(oauth_request)
+        # get the access token
+        token = self._get_token(oauth_request, 'access')
+        self._check_signature(oauth_request, consumer, token)
+        parameters = oauth_request.get_nonoauth_parameters()
+        return consumer, token, parameters
+
+    # authorize a request token
+    def authorize_token(self, token, user):
+        return self.data_store.authorize_request_token(token, user)
+    
+    # get the callback url
+    def get_callback(self, oauth_request):
+        return oauth_request.get_parameter('oauth_callback')
+
+    # optional support for the authenticate header   
+    def build_authenticate_header(self, realm=''):
+        return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+    # verify the correct version request for this server
+    def _get_version(self, oauth_request):
+        try:
+            version = oauth_request.get_parameter('oauth_version')
+        except:
+            version = VERSION
+        if version and version != self.version:
+            raise OAuthError('OAuth version %s not supported.' % str(version))
+        return version
+
+    # figure out the signature with some defaults
+    def _get_signature_method(self, oauth_request):
+        try:
+            signature_method = oauth_request.get_parameter('oauth_signature_method')
+        except:
+            signature_method = SIGNATURE_METHOD
+        try:
+            # get the signature method object
+            signature_method = self.signature_methods[signature_method]
+        except:
+            signature_method_names = ', '.join(self.signature_methods.keys())
+            raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
+
+        return signature_method
+
+    def _get_consumer(self, oauth_request):
+        consumer_key = oauth_request.get_parameter('oauth_consumer_key')
+        if not consumer_key:
+            raise OAuthError('Invalid consumer key.')
+        consumer = self.data_store.lookup_consumer(consumer_key)
+        if not consumer:
+            raise OAuthError('Invalid consumer.')
+        return consumer
+
+    # try to find the token for the provided request token key
+    def _get_token(self, oauth_request, token_type='access'):
+        token_field = oauth_request.get_parameter('oauth_token')
+        token = self.data_store.lookup_token(token_type, token_field)
+        if not token:
+            raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
+        return token
+
+    def _check_signature(self, oauth_request, consumer, token):
+        timestamp, nonce = oauth_request._get_timestamp_nonce()
+        self._check_timestamp(timestamp)
+        self._check_nonce(consumer, token, nonce)
+        signature_method = self._get_signature_method(oauth_request)
+        try:
+            signature = oauth_request.get_parameter('oauth_signature')
+        except:
+            raise OAuthError('Missing signature.')
+        # validate the signature
+        valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature)
+        if not valid_sig:
+            key, base = signature_method.build_signature_base_string(oauth_request, consumer, token)
+            raise OAuthError('Invalid signature. Expected signature base string: %s' % base)
+        built = signature_method.build_signature(oauth_request, consumer, token)
+
+    def _check_timestamp(self, timestamp):
+        # verify that timestamp is recentish
+        timestamp = int(timestamp)
+        now = int(time.time())
+        lapsed = now - timestamp
+        if lapsed > self.timestamp_threshold:
+            raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
+
+    def _check_nonce(self, consumer, token, nonce):
+        # verify that the nonce is uniqueish
+        nonce = self.data_store.lookup_nonce(consumer, token, nonce)
+        if nonce:
+            raise OAuthError('Nonce already used: %s' % str(nonce))
+
+# OAuthClient is a worker to attempt to execute a request
+class OAuthClient(object):
+    consumer = None
+    token = None
+
+    def __init__(self, oauth_consumer, oauth_token):
+        self.consumer = oauth_consumer
+        self.token = oauth_token
+
+    def get_consumer(self):
+        return self.consumer
+
+    def get_token(self):
+        return self.token
+
+    def fetch_request_token(self, oauth_request):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_access_token(self, oauth_request):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def access_resource(self, oauth_request):
+        # -> some protected resource
+        raise NotImplementedError
+
+# OAuthDataStore is a database abstraction used to lookup consumers and tokens
+class OAuthDataStore(object):
+
+    def lookup_consumer(self, key):
+        # -> OAuthConsumer
+        raise NotImplementedError
+
+    def lookup_token(self, oauth_consumer, token_type, token_token):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_request_token(self, oauth_consumer):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_access_token(self, oauth_consumer, oauth_token):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def authorize_request_token(self, oauth_token, user):
+        # -> OAuthToken
+        raise NotImplementedError
+
+# OAuthSignatureMethod is a strategy class that implements a signature method
+class OAuthSignatureMethod(object):
+    def get_name(self):
+        # -> str
+        raise NotImplementedError
+
+    def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
+        # -> str key, str raw
+        raise NotImplementedError
+
+    def build_signature(self, oauth_request, oauth_consumer, oauth_token):
+        # -> str
+        raise NotImplementedError
+
+    def check_signature(self, oauth_request, consumer, token, signature):
+        built = self.build_signature(oauth_request, consumer, token)
+        return built == signature
+
+class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
+
+    def get_name(self):
+        return 'HMAC-SHA1'
+        
+    def build_signature_base_string(self, oauth_request, consumer, token):
+        sig = (
+            escape(oauth_request.get_normalized_http_method()),
+            escape(oauth_request.get_normalized_http_url()),
+            escape(oauth_request.get_normalized_parameters()),
+        )
+
+        key = '%s&' % escape(consumer.secret)
+        if token:
+            key += escape(token.secret)
+        raw = '&'.join(sig)
+        return key, raw
+
+    def build_signature(self, oauth_request, consumer, token):
+        # build the base signature string
+        key, raw = self.build_signature_base_string(oauth_request, consumer, token)
+
+        # hmac object
+        try:
+            import hashlib # 2.5
+            hashed = hmac.new(key, raw, hashlib.sha1)
+        except:
+            import sha # deprecated
+            hashed = hmac.new(key, raw, sha)
+
+        # calculate the digest base 64
+        return binascii.b2a_base64(hashed.digest())[:-1]
+
+class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
+
+    def get_name(self):
+        return 'PLAINTEXT'
+
+    def build_signature_base_string(self, oauth_request, consumer, token):
+        # concatenate the consumer key and secret
+        sig = escape(consumer.secret) + '&'
+        if token:
+            sig = sig + escape(token.secret)
+        return sig
+
+    def build_signature(self, oauth_request, consumer, token):
+        return self.build_signature_base_string(oauth_request, consumer, token)

Added: trunk/conduit/modules/GoogleModule/gdata/oauth/rsa.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/oauth/rsa.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+
+"""
+requires tlslite - http://trevp.net/tlslite/
+
+"""
+
+import binascii
+
+from gdata.tlslite.utils import keyfactory
+from gdata.tlslite.utils import cryptomath
+
+# XXX andy: ugly local import due to module name, oauth.oauth
+import gdata.oauth as oauth
+
+class OAuthSignatureMethod_RSA_SHA1(oauth.OAuthSignatureMethod):
+  def get_name(self):
+    return "RSA-SHA1"
+
+  def _fetch_public_cert(self, oauth_request):
+    # not implemented yet, ideas are:
+    # (1) do a lookup in a table of trusted certs keyed off of consumer
+    # (2) fetch via http using a url provided by the requester
+    # (3) some sort of specific discovery code based on request
+    #
+    # either way should return a string representation of the certificate
+    raise NotImplementedError
+
+  def _fetch_private_cert(self, oauth_request):
+    # not implemented yet, ideas are:
+    # (1) do a lookup in a table of trusted certs keyed off of consumer
+    #
+    # either way should return a string representation of the certificate
+    raise NotImplementedError
+
+  def build_signature_base_string(self, oauth_request, consumer, token):
+      sig = (
+          oauth.escape(oauth_request.get_normalized_http_method()),
+          oauth.escape(oauth_request.get_normalized_http_url()),
+          oauth.escape(oauth_request.get_normalized_parameters()),
+      )
+      key = ''
+      raw = '&'.join(sig)
+      return key, raw
+
+  def build_signature(self, oauth_request, consumer, token):
+    key, base_string = self.build_signature_base_string(oauth_request,
+                                                        consumer,
+                                                        token)
+
+    # Fetch the private key cert based on the request
+    cert = self._fetch_private_cert(oauth_request)
+
+    # Pull the private key from the certificate
+    privatekey = keyfactory.parsePrivateKey(cert)
+    
+    # Convert base_string to bytes
+    #base_string_bytes = cryptomath.createByteArraySequence(base_string)
+    
+    # Sign using the key
+    signed = privatekey.hashAndSign(base_string)
+  
+    return binascii.b2a_base64(signed)[:-1]
+  
+  def check_signature(self, oauth_request, consumer, token, signature):
+    decoded_sig = base64.b64decode(signature);
+
+    key, base_string = self.build_signature_base_string(oauth_request,
+                                                        consumer,
+                                                        token)
+
+    # Fetch the public key cert based on the request
+    cert = self._fetch_public_cert(oauth_request)
+
+    # Pull the public key from the certificate
+    publickey = keyfactory.parsePEMKey(cert, public=True)
+
+    # Check the signature
+    ok = publickey.hashAndVerify(decoded_sig, base_string)
+
+    return ok
+
+
+class TestOAuthSignatureMethod_RSA_SHA1(OAuthSignatureMethod_RSA_SHA1):
+  def _fetch_public_cert(self, oauth_request):
+    cert = """
+-----BEGIN CERTIFICATE-----
+MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0
+IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV
+BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY
+zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb
+mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3
+DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d
+4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb
+WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J
+-----END CERTIFICATE-----
+"""
+    return cert
+
+  def _fetch_private_cert(self, oauth_request):
+    cert = """
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
+A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
+7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
+hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
+X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
+uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
+rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
+zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
+qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
+WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
+cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
+3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
+AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
+Lw03eHTNQghS0A==
+-----END PRIVATE KEY-----
+"""
+    return cert

Modified: trunk/conduit/modules/GoogleModule/gdata/photos/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/photos/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/photos/service.py	Tue Mar 17 09:00:35 2009
@@ -124,31 +124,26 @@
 class PhotosService(gdata.service.GDataService):
   userUri = '/data/feed/api/user/%s'
   
-  def __init__(self, email=None, password=None, 
-    source=None, server='picasaweb.google.com', additional_headers=None):
-    """ GooglePhotosService constructor.
-      
-    Arguments:
-    email: string (optional) The e-mail address of the account to use for
-           authentication.
-    password: string (optional) The password of the account to use for
-              authentication.
-    source: string (optional) The name of the user's application.
-    server: string (optional) The server the feed is hosted on.
-    additional_headers: dict (optional) Any additional HTTP headers to be
-                        transmitted to the service in the form of key-value
-                        pairs.
-
-    Returns:
-    A PhotosService object used to communicate with the Google Photos
-    service.
+  def __init__(self, email=None, password=None, source=None,
+               server='picasaweb.google.com', additional_headers=None,
+               **kwargs):
+    """Creates a client for the Google Photos service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'picasaweb.google.com'.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
     """
     self.email = email
     self.client = source
-    gdata.service.GDataService.__init__(self, email=self.email, password=password,
-                                        service='lh2', source=source,
-                                        server=server,
-                                        additional_headers=additional_headers)
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='lh2', source=source,
+        server=server, additional_headers=additional_headers, **kwargs)
 
   def GetFeed(self, uri, limit=None, start_index=None):
     """Get a feed.

Modified: trunk/conduit/modules/GoogleModule/gdata/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/service.py	Tue Mar 17 09:00:35 2009
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 #
-# Copyright (C) 2006 Google Inc.
+# Copyright (C) 2006,2008 Google Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -33,7 +33,11 @@
 
   NonAuthSubToken: Raised if a method to modify an AuthSub token is used when
                    the user is either not authenticated or is authenticated
-                   through programmatic login.
+                   through another authentication mechanism.
+
+  NonOAuthToken: Raised if a method to modify an OAuth token is used when the
+                 user is either not authenticated or is authenticated through
+                 another authentication mechanism.
 
   RequestError: Raised if a CRUD request returned a non-success code. 
 
@@ -58,8 +62,8 @@
 
 
 import re
-import httplib
 import urllib
+import urlparse
 try:
   from xml.etree import cElementTree as ElementTree
 except ImportError:
@@ -73,17 +77,83 @@
 import atom.service
 import gdata
 import atom
+import atom.http_interface
+import atom.token_store
 import gdata.auth
 
 
-PROGRAMMATIC_AUTH_LABEL = 'GoogleLogin auth'
-AUTHSUB_AUTH_LABEL = 'AuthSub token'
 AUTH_SERVER_HOST = 'https://www.google.com'
 
 
+# When requesting an AuthSub token, it is often helpful to track the scope
+# which is being requested. One way to accomplish this is to add a URL 
+# parameter to the 'next' URL which contains the requested scope. This
+# constant is the default name (AKA key) for the URL parameter.
+SCOPE_URL_PARAM_NAME = 'authsub_token_scope'
+# When requesting an OAuth access token or authorization of an existing OAuth
+# request token, it is often helpful to track the scope(s) which is/are being
+# requested. One way to accomplish this is to add a URL parameter to the
+# 'callback' URL which contains the requested scope. This constant is the
+# default name (AKA key) for the URL parameter.
+OAUTH_SCOPE_URL_PARAM_NAME = 'oauth_token_scope'
+# Maps the service names used in ClientLogin to scope URLs.
+CLIENT_LOGIN_SCOPES = {
+    'cl': [ # Google Calendar
+        'https://www.google.com/calendar/feeds/',
+        'http://www.google.com/calendar/feeds/'],
+    'gbase': [ # Google Base
+        'http://base.google.com/base/feeds/',
+        'http://www.google.com/base/feeds/'], 
+    'blogger': [ # Blogger
+        'http://www.blogger.com/feeds/'], 
+    'codesearch': [ # Google Code Search
+        'http://www.google.com/codesearch/feeds/'],
+    'cp': [ # Contacts API
+        'https://www.google.com/m8/feeds/',
+        'http://www.google.com/m8/feeds/'],
+    'finance': [ # Google Finance
+        'http://finance.google.com/finance/feeds/'],
+    'health': [ # Google Health
+        'https://www.google.com/health/feeds/'],
+    'writely': [ # Documents List API
+        'https://docs.google.com/feeds/',
+        'http://docs.google.com/feeds/'],
+    'lh2': [ # Picasa Web Albums
+        'http://picasaweb.google.com/data/'],
+    'apps': [ # Google Apps Provisioning API
+        'http://www.google.com/a/feeds/',
+        'https://www.google.com/a/feeds/',
+        'http://apps-apis.google.com/a/feeds/',
+        'https://apps-apis.google.com/a/feeds/'],
+    'weaver': [ # Health H9 Sandbox
+        'https://www.google.com/h9/'],
+    'wise': [ # Spreadsheets Data API
+        'https://spreadsheets.google.com/feeds/',
+        'http://spreadsheets.google.com/feeds/'],
+    'sitemaps': [ # Google Webmaster Tools
+        'https://www.google.com/webmasters/tools/feeds/'],
+    'youtube': [ # YouTube
+        'http://gdata.youtube.com/feeds/api/',
+        'http://uploads.gdata.youtube.com/feeds/api',
+        'http://gdata.youtube.com/action/GetUploadToken']}
+
+
+def lookup_scopes(service_name):
+  """Finds the scope URLs for the desired service.
+  
+  In some cases, an unknown service may be used, and in those cases this
+  function will return None.
+  """
+  if service_name in CLIENT_LOGIN_SCOPES:
+    return CLIENT_LOGIN_SCOPES[service_name]
+  return None
+    
+
 # Module level variable specifies which module should be used by GDataService
 # objects to make HttpRequests. This setting can be overridden on each 
 # instance of GDataService.
+# This module level variable is deprecated. Reassign the http_client member
+# of a GDataService object instead.
 http_request_handler = atom.service
 
 
@@ -107,6 +177,10 @@
   pass
 
 
+class NonOAuthToken(Error):
+  pass
+
+
 class RequestError(Error):
   pass
 
@@ -114,19 +188,48 @@
 class UnexpectedReturnType(Error):
   pass
 
+
 class BadAuthenticationServiceURL(Error):
   pass
 
+
+class FetchingOAuthRequestTokenFailed(RequestError):
+  pass
+
+
+class TokenUpgradeFailed(RequestError):
+  pass
+
+
+class RevokingOAuthTokenFailed(RequestError):
+  pass
+
+
+class AuthorizationRequired(Error):
+  pass
+
+
+class TokenHadNoScope(Error):
+  pass
+
+
 class GDataService(atom.service.AtomService):
   """Contains elements needed for GData login and CRUD request headers.
 
   Maintains additional headers (tokens for example) needed for the GData 
   services to allow a user to perform inserts, updates, and deletes.
   """
+  # The hander member is deprecated, use http_client instead.
+  handler = None
+  # The auth_token member is deprecated, use the token_store instead.
+  auth_token = None
+  # The tokens dict is deprecated in favor of the token_store.
+  tokens = None
 
   def __init__(self, email=None, password=None, account_type='HOSTED_OR_GOOGLE',
                service=None, auth_service_url=None, source=None, server=None, 
-               additional_headers=None, handler=None):
+               additional_headers=None, handler=None, tokens=None,
+               http_client=None, token_store=None):
     """Creates an object of type GDataService.
 
     Args:
@@ -147,11 +250,20 @@
           will be opened. Default value: 'base.google.com'.
       additional_headers: dictionary (optional) Any additional headers which 
           should be included with CRUD operations.
-      handler: module (optional) The module whose HttpRequest function 
-          should be used when making requests to the server. The default 
-          value is atom.service.
+      handler: module (optional) This parameter is deprecated and has been
+          replaced by http_client.
+      tokens: This parameter is deprecated, calls should be made to 
+          token_store instead.
+      http_client: An object responsible for making HTTP requests using a
+          request method. If none is provided, a new instance of
+          atom.http.ProxiedHttpClient will be used.
+      token_store: Keeps a collection of authorization tokens which can be
+          applied to requests for a specific URLs. Critical methods are
+          find_token based on a URL (atom.url.Url or a string), add_token,
+          and remove_token.
     """
-
+    atom.service.AtomService.__init__(self, http_client=http_client, 
+        token_store=token_store)
     self.email = email
     self.password = password
     self.account_type = account_type
@@ -159,32 +271,24 @@
     self.auth_service_url = auth_service_url
     self.server = server
     self.additional_headers = additional_headers or {}
-    self.handler = handler or http_request_handler
+    self._oauth_input_params = None
     self.__SetSource(source)
-    self.__auth_token = None
     self.__captcha_token = None
     self.__captcha_url = None
     self.__gsessionid = None
+
+    if http_request_handler.__name__ == 'gdata.urlfetch':
+      import gdata.alt.appengine
+      self.http_client = gdata.alt.appengine.AppEngineHttpClient()
  
   # Define properties for GDataService
-  def _SetAuthSubToken(self, auth_token):
-    """Sets the token sent in requests to an AuthSub token.
-
-    Only use this method if you have received a token from the AuthSub 
-    service. The auth_token is set automatically when ProgrammaticLogin()
-    is used. See documentation for Google AuthSub here:
-    http://code.google.com/apis/accounts/AuthForWebApps.html .
-
-    Args:
-      auth_token: string The token returned by the AuthSub service.
-    """
-
-    self.__auth_token = '%s=%s' % (AUTHSUB_AUTH_LABEL, auth_token)
-    # The auth token is only set externally when using AuthSub authentication,
-    # so set the auth_type to indicate AuthSub.
-
-  def __SetAuthSubToken(self, auth_token):
-    self._SetAuthSubToken(auth_token)
+  def _SetAuthSubToken(self, auth_token, scopes=None):
+    """Deprecated, use SetAuthSubToken instead."""
+    self.SetAuthSubToken(auth_token, scopes=scopes)
+
+  def __SetAuthSubToken(self, auth_token, scopes=None):
+    """Deprecated, use SetAuthSubToken instead."""
+    self._SetAuthSubToken(auth_token, scopes=scopes)
 
   def _GetAuthToken(self):
     """Returns the auth token used for authenticating requests.
@@ -192,14 +296,12 @@
     Returns:
       string
     """
-
-    return self.__auth_token
-
-  def __GetAuthToken(self):
-    return self._GetAuthToken()
-
-  auth_token = property(__GetAuthToken, __SetAuthSubToken,
-      doc="""Get or set the token used for authentication.""")
+    current_scopes = lookup_scopes(self.service)
+    if current_scopes:
+      token = self.token_store.find_token(current_scopes[0])
+      if hasattr(token, 'auth_header'):
+        return token.auth_header
+    return None
 
   def _GetCaptchaToken(self):
     """Returns a captcha token if the most recent login attempt generated one.
@@ -210,7 +312,6 @@
     Returns:
       string
     """
-
     return self.__captcha_token
 
   def __GetCaptchaToken(self):
@@ -228,7 +329,6 @@
     Returns:
       string
     """
-
     return self.__captcha_url
 
   def __GetCaptchaURL(self):
@@ -237,38 +337,363 @@
   captcha_url = property(__GetCaptchaURL,
       doc="""Get the captcha URL for a login request.""")
 
+  def SetOAuthInputParameters(self, signature_method, consumer_key,
+                              consumer_secret=None, rsa_key=None,
+                              two_legged_oauth=False):
+    """Sets parameters required for using OAuth authentication mechanism.
+    
+    NOTE: Though consumer_secret and rsa_key are optional, either of the two
+    is required depending on the value of the signature_method.
+    
+    Args:
+      signature_method: class which provides implementation for strategy class
+          oauth.oauth.OAuthSignatureMethod. Signature method to be used for
+          signing each request. Valid implementations are provided as the
+          constants defined by gdata.auth.OAuthSignatureMethod. Currently
+          they are gdata.auth.OAuthSignatureMethod.RSA_SHA1 and
+          gdata.auth.OAuthSignatureMethod.HMAC_SHA1
+      consumer_key: string Domain identifying third_party web application.
+      consumer_secret: string (optional) Secret generated during registration.
+          Required only for HMAC_SHA1 signature method.
+      rsa_key: string (optional) Private key required for RSA_SHA1 signature
+          method.
+      two_legged_oauth: string (default=False) Enables two-legged OAuth process.
+    """
+    self._oauth_input_params = gdata.auth.OAuthInputParams(
+        signature_method, consumer_key, consumer_secret=consumer_secret,
+        rsa_key=rsa_key)
+    if two_legged_oauth:
+      oauth_token = gdata.auth.OAuthToken(
+          oauth_input_params=self._oauth_input_params)
+      self.SetOAuthToken(oauth_token)
+  
+  def FetchOAuthRequestToken(self, scopes=None, extra_parameters=None,
+                             request_url='%s/accounts/OAuthGetRequestToken' % \
+                             AUTH_SERVER_HOST):
+    """Fetches OAuth request token and returns it.
+    
+    Args:
+      scopes: string or list of string base URL(s) of the service(s) to be
+          accessed. If None, then this method tries to determine the
+          scope(s) from the current service.
+      extra_parameters: dict (optional) key-value pairs as any additional
+          parameters to be included in the URL and signature while making a
+          request for fetching an OAuth request token. All the OAuth parameters
+          are added by default. But if provided through this argument, any
+          default parameters will be overwritten. For e.g. a default parameter
+          oauth_version 1.0 can be overwritten if
+          extra_parameters = {'oauth_version': '2.0'}
+      request_url: Request token URL. The default is
+          'https://www.google.com/accounts/OAuthGetRequestToken'.
+      
+    Returns:
+      The fetched request token as a gdata.auth.OAuthToken object.
+      
+    Raises:
+      FetchingOAuthRequestTokenFailed if the server responded to the request
+      with an error.
+    """
+    if scopes is None:
+      scopes = lookup_scopes(self.service)
+    if not isinstance(scopes, (list, tuple)):
+      scopes = [scopes,]
+    request_token_url = gdata.auth.GenerateOAuthRequestTokenUrl(
+        self._oauth_input_params, scopes,
+        request_token_url=request_url,
+        extra_parameters=extra_parameters)
+    response = self.http_client.request('GET', str(request_token_url))
+    if response.status == 200:
+      token = gdata.auth.OAuthToken()
+      token.set_token_string(response.read())
+      token.scopes = scopes
+      token.oauth_input_params = self._oauth_input_params
+      return token
+    error = {
+        'status': response.status,
+        'reason': 'Non 200 response on upgrade',
+        'body': response.read()
+        }
+    raise FetchingOAuthRequestTokenFailed(error)    
+  
+  def SetOAuthToken(self, oauth_token):
+    """Attempts to set the current token and add it to the token store.
+    
+    The oauth_token can be any OAuth token i.e. unauthorized request token,
+    authorized request token or access token.
+    This method also attempts to add the token to the token store.
+    Use this method any time you want the current token to point to the
+    oauth_token passed. For e.g. call this method with the request token
+    you receive from FetchOAuthRequestToken.
+    
+    Args:
+      request_token: gdata.auth.OAuthToken OAuth request token.
+    """
+    if self.auto_set_current_token:
+      self.current_token = oauth_token
+    if self.auto_store_tokens:
+      self.token_store.add_token(oauth_token)
+    
+  def GenerateOAuthAuthorizationURL(
+      self, request_token=None, callback_url=None, extra_params=None,
+      include_scopes_in_callback=False,
+      scopes_param_prefix=OAUTH_SCOPE_URL_PARAM_NAME,
+      request_url='%s/accounts/OAuthAuthorizeToken' % AUTH_SERVER_HOST):
+    """Generates URL at which user will login to authorize the request token.
+    
+    Args:
+      request_token: gdata.auth.OAuthToken (optional) OAuth request token.
+          If not specified, then the current token will be used if it is of
+          type <gdata.auth.OAuthToken>, else it is found by looking in the
+          token_store by looking for a token for the current scope.    
+      callback_url: string (optional) The URL user will be sent to after
+          logging in and granting access.
+      extra_params: dict (optional) Additional parameters to be sent.
+      include_scopes_in_callback: Boolean (default=False) if set to True, and
+          if 'callback_url' is present, the 'callback_url' will be modified to
+          include the scope(s) from the request token as a URL parameter. The
+          key for the 'callback' URL's scope parameter will be
+          OAUTH_SCOPE_URL_PARAM_NAME. The benefit of including the scope URL as
+          a parameter to the 'callback' URL, is that the page which receives
+          the OAuth token will be able to tell which URLs the token grants
+          access to.
+      scopes_param_prefix: string (default='oauth_token_scope') The URL
+          parameter key which maps to the list of valid scopes for the token.
+          This URL parameter will be included in the callback URL along with
+          the scopes of the token as value if include_scopes_in_callback=True.
+      request_url: Authorization URL. The default is
+          'https://www.google.com/accounts/OAuthAuthorizeToken'.
+    Returns:
+      A string URL at which the user is required to login.
+    
+    Raises:
+      NonOAuthToken if the user's request token is not an OAuth token or if a
+      request token was not available.
+    """
+    if request_token and not isinstance(request_token, gdata.auth.OAuthToken):
+      raise NonOAuthToken
+    if not request_token:
+      if isinstance(self.current_token, gdata.auth.OAuthToken):
+        request_token = self.current_token
+      else:
+        current_scopes = lookup_scopes(self.service)
+        if current_scopes:
+          token = self.token_store.find_token(current_scopes[0])
+          if isinstance(token, gdata.auth.OAuthToken):
+            request_token = token
+    if not request_token:
+      raise NonOAuthToken
+    return str(gdata.auth.GenerateOAuthAuthorizationUrl(
+        request_token,
+        authorization_url=request_url,
+        callback_url=callback_url, extra_params=extra_params,
+        include_scopes_in_callback=include_scopes_in_callback,
+        scopes_param_prefix=scopes_param_prefix))   
+  
+  def UpgradeToOAuthAccessToken(self, authorized_request_token=None,
+                                request_url='%s/accounts/OAuthGetAccessToken' \
+                                % AUTH_SERVER_HOST, oauth_version='1.0'):
+    """Upgrades the authorized request token to an access token.
+    
+    Args:
+      authorized_request_token: gdata.auth.OAuthToken (optional) OAuth request
+          token. If not specified, then the current token will be used if it is
+          of type <gdata.auth.OAuthToken>, else it is found by looking in the
+          token_store by looking for a token for the current scope.
+      oauth_version: str (default='1.0') oauth_version parameter. All other
+          'oauth_' parameters are added by default. This parameter too, is
+          added by default but here you can override it's value.
+      request_url: Access token URL. The default is
+          'https://www.google.com/accounts/OAuthGetAccessToken'.
+          
+    Raises:
+      NonOAuthToken if the user's authorized request token is not an OAuth
+      token or if an authorized request token was not available.
+      TokenUpgradeFailed if the server responded to the request with an 
+      error.
+    """
+    if (authorized_request_token and
+        not isinstance(authorized_request_token, gdata.auth.OAuthToken)):
+      raise NonOAuthToken
+    if not authorized_request_token:
+      if isinstance(self.current_token, gdata.auth.OAuthToken):
+        authorized_request_token = self.current_token
+      else:
+        current_scopes = lookup_scopes(self.service)
+        if current_scopes:
+          token = self.token_store.find_token(current_scopes[0])
+          if isinstance(token, gdata.auth.OAuthToken):
+            authorized_request_token = token
+    if not authorized_request_token:
+      raise NonOAuthToken
+    access_token_url = gdata.auth.GenerateOAuthAccessTokenUrl(
+        authorized_request_token,
+        self._oauth_input_params,
+        access_token_url=request_url,
+        oauth_version=oauth_version)
+    response = self.http_client.request('GET', str(access_token_url))
+    if response.status == 200:
+      token = gdata.auth.OAuthTokenFromHttpBody(response.read())
+      token.scopes = authorized_request_token.scopes
+      token.oauth_input_params = authorized_request_token.oauth_input_params
+      self.SetOAuthToken(token)
+    else:
+      raise TokenUpgradeFailed({'status': response.status,
+                                'reason': 'Non 200 response on upgrade',
+                                'body': response.read()})      
+  
+  def RevokeOAuthToken(self, request_url='%s/accounts/AuthSubRevokeToken' % \
+                       AUTH_SERVER_HOST):
+    """Revokes an existing OAuth token.
+
+    request_url: Token revoke URL. The default is
+          'https://www.google.com/accounts/AuthSubRevokeToken'.
+    Raises:
+      NonOAuthToken if the user's auth token is not an OAuth token.
+      RevokingOAuthTokenFailed if request for revoking an OAuth token failed.
+    """
+    scopes = lookup_scopes(self.service)
+    token = self.token_store.find_token(scopes[0])
+    if not isinstance(token, gdata.auth.OAuthToken):
+      raise NonOAuthToken
+
+    response = token.perform_request(self.http_client, 'GET', request_url,
+        headers={'Content-Type':'application/x-www-form-urlencoded'})
+    if response.status == 200:
+      self.token_store.remove_token(token)
+    else:
+      raise RevokingOAuthTokenFailed
+  
   def GetAuthSubToken(self):
-    """Returns the AuthSub Token after removing the AuthSub Authorization
-    Label.
+    """Returns the AuthSub token as a string.
      
-    The AuthSub Authorization Label reads: "AuthSub token"
+    If the token is an gdta.auth.AuthSubToken, the Authorization Label
+    ("AuthSub token") is removed.
 
+    This method examines the current_token to see if it is an AuthSubToken
+    or SecureAuthSubToken. If not, it searches the token_store for a token
+    which matches the current scope.
+    
+    The current scope is determined by the service name string member.
+    
     Returns:
-      If the AuthSub Token is set AND it begins with the AuthSub 
-      Authorization Label, the AuthSub Token is returned minus the AuthSub
-      Label. If the AuthSub Token does not start with the AuthSub
-      Authorization Label or it is not set, None is returned.
-    """
-    if self.__auth_token.startswith(AUTHSUB_AUTH_LABEL):
-      # Strip off the leading 'AUTHSUB_AUTH_LABEL=' and just return the
-      # token value.
-      return self.__auth_token[len(AUTHSUB_AUTH_LABEL)+1:]
-    else:
+      If the current_token is set to an AuthSubToken/SecureAuthSubToken,
+      return the token string. If there is no current_token, a token string
+      for a token which matches the service object's default scope is returned.
+      If there are no tokens valid for the scope, returns None.
+    """
+    if isinstance(self.current_token, gdata.auth.AuthSubToken):
+      return self.current_token.get_token_string()
+    current_scopes = lookup_scopes(self.service)
+    if current_scopes:
+      token = self.token_store.find_token(current_scopes[0])
+      if isinstance(token, gdata.auth.AuthSubToken):
+        return token.get_token_string()
+    else:
+      token = self.token_store.find_token(atom.token_store.SCOPE_ALL)
+      if isinstance(token, gdata.auth.ClientLoginToken):
+        return token.get_token_string()
       return None
 
-  def SetAuthSubToken(self, token):
-    self.__auth_token = '%s=%s' % (AUTHSUB_AUTH_LABEL, token)
+  def SetAuthSubToken(self, token, scopes=None, rsa_key=None):
+    """Sets the token sent in requests to an AuthSub token.
+
+    Sets the current_token and attempts to add the token to the token_store.
+    
+    Only use this method if you have received a token from the AuthSub
+    service. The auth token is set automatically when UpgradeToSessionToken()
+    is used. See documentation for Google AuthSub here:
+    http://code.google.com/apis/accounts/AuthForWebApps.html 
+
+    Args:
+     token: gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken or string
+            The token returned by the AuthSub service. If the token is an
+            AuthSubToken or SecureAuthSubToken, the scope information stored in
+            the token is used. If the token is a string, the scopes parameter is
+            used to determine the valid scopes.
+     scopes: list of URLs for which the token is valid. This is only used
+             if the token parameter is a string.
+     rsa_key: string (optional) Private key required for RSA_SHA1 signature
+              method.  This parameter is necessary if the token is a string
+              representing a secure token.
+    """
+    if not isinstance(token, gdata.auth.AuthSubToken):
+      token_string = token
+      if rsa_key:
+        token = gdata.auth.SecureAuthSubToken(rsa_key)
+      else:
+        token = gdata.auth.AuthSubToken()
+
+      token.set_token_string(token_string)
+        
+    # If no scopes were set for the token, use the scopes passed in, or
+    # try to determine the scopes based on the current service name. If
+    # all else fails, set the token to match all requests.
+    if not token.scopes:
+      if scopes is None:
+        scopes = lookup_scopes(self.service)
+        if scopes is None:
+          scopes = [atom.token_store.SCOPE_ALL]
+      token.scopes = scopes
+    if self.auto_set_current_token:
+      self.current_token = token
+    if self.auto_store_tokens:
+      self.token_store.add_token(token)
 
   def GetClientLoginToken(self):
-    if self.__auth_token.startswith(PROGRAMMATIC_AUTH_LABEL):
-      # Strip off the leading 'PROGRAMMATIC_AUTH_LABEL=' and just return the
-      # token value.
-      return self.__auth_token[len(PROGRAMMATIC_AUTH_LABEL)+1:]
-    else:
+    """Returns the token string for the current token or a token matching the 
+    service scope.
+
+    If the current_token is a ClientLoginToken, the token string for 
+    the current token is returned. If the current_token is not set, this method
+    searches for a token in the token_store which is valid for the service 
+    object's current scope.
+
+    The current scope is determined by the service name string member.
+    The token string is the end of the Authorization header, it doesn not
+    include the ClientLogin label.
+    """
+    if isinstance(self.current_token, gdata.auth.ClientLoginToken):
+      return self.current_token.get_token_string()
+    current_scopes = lookup_scopes(self.service)
+    if current_scopes:
+      token = self.token_store.find_token(current_scopes[0])
+      if isinstance(token, gdata.auth.ClientLoginToken):
+        return token.get_token_string()
+    else:
+      token = self.token_store.find_token(atom.token_store.SCOPE_ALL)
+      if isinstance(token, gdata.auth.ClientLoginToken):
+        return token.get_token_string()
       return None
 
-  def SetClientLoginToken(self, token):
-    self.__auth_token = '%s=%s' % (PROGRAMMATIC_AUTH_LABEL, token)
+  def SetClientLoginToken(self, token, scopes=None):
+    """Sets the token sent in requests to a ClientLogin token.
+
+    This method sets the current_token to a new ClientLoginToken and it 
+    also attempts to add the ClientLoginToken to the token_store.
+    
+    Only use this method if you have received a token from the ClientLogin
+    service. The auth_token is set automatically when ProgrammaticLogin()
+    is used. See documentation for Google ClientLogin here:
+    http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html
+
+    Args:
+      token: string or instance of a ClientLoginToken. 
+    """
+    if not isinstance(token, gdata.auth.ClientLoginToken):
+      token_string = token
+      token = gdata.auth.ClientLoginToken()
+      token.set_token_string(token_string)
+
+    if not token.scopes:
+      if scopes is None:
+        scopes = lookup_scopes(self.service)
+        if scopes is None:
+          scopes = [atom.token_store.SCOPE_ALL]
+      token.scopes = scopes
+    if self.auto_set_current_token:
+      self.current_token = token
+    if self.auto_store_tokens:
+      self.token_store.add_token(token)
 
   # Private methods to create the source property.
   def __GetSource(self):
@@ -277,8 +702,8 @@
   def __SetSource(self, new_source):
     self.__source = new_source
     # Update the UserAgent header to include the new application name.
-    self.additional_headers['User-Agent'] = '%s GData-Python/1.0.13' % (
-        self.__source)
+    self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % (
+        self.__source,)
 
   source = property(__GetSource, __SetSource, 
       doc="""The source is the name of the application making the request. 
@@ -308,8 +733,8 @@
       BadAuthentication if the login service rejected the username or password
       Error if the login service responded with a 403 different from the above
     """
-    request_body = gdata.auth.GenerateClientLoginRequestBody(self.email, 
-        self.password, self.service, self.source, self.account_type, 
+    request_body = gdata.auth.generate_client_login_request_body(self.email,
+        self.password, self.service, self.source, self.account_type,
         captcha_token, captcha_response)
 
     # If the user has defined their own authentication service URL, 
@@ -319,22 +744,22 @@
     else:
         auth_request_url = self.auth_service_url
 
-    auth_response = self.handler.HttpRequest(self, 'POST', request_body, 
-        auth_request_url,
-        extra_headers={'Content-Length':str(len(request_body))},
-        content_type='application/x-www-form-urlencoded')
+    auth_response = self.http_client.request('POST', auth_request_url,
+        data=request_body, 
+        headers={'Content-Type':'application/x-www-form-urlencoded'})
     response_body = auth_response.read()
 
     if auth_response.status == 200:
-      self.__auth_token = gdata.auth.GenerateClientLoginAuthToken(
-           response_body)
+      # TODO: insert the token into the token_store directly.
+      self.SetClientLoginToken(
+          gdata.auth.get_client_login_token(response_body))
       self.__captcha_token = None
       self.__captcha_url = None
 
     elif auth_response.status == 403:
       # Examine each line to find the error type and the captcha token and
       # captch URL if they are present.
-      captcha_parameters = gdata.auth.GetCaptchChallenge(response_body, 
+      captcha_parameters = gdata.auth.get_captcha_challenge(response_body,
           captcha_base_url='%s/accounts/' % AUTH_SERVER_HOST)
       if captcha_parameters:
         self.__captcha_token = captcha_parameters['token']
@@ -357,7 +782,8 @@
       raise BadAuthenticationServiceURL, 'Server responded with a 302 code.'
 
   def ClientLogin(self, username, password, account_type=None, service=None,
-      auth_service_url=None, source=None, captcha_token=None, captcha_response=None):
+      auth_service_url=None, source=None, captcha_token=None, 
+      captcha_response=None):
     """Convenience method for authenticating using ProgrammaticLogin. 
     
     Sets values for email, password, and other optional members.
@@ -385,58 +811,83 @@
 
     self.ProgrammaticLogin(captcha_token, captcha_response)
 
-  def GenerateAuthSubURL(self, next, scope, secure=False, session=True):
+  def GenerateAuthSubURL(self, next, scope, secure=False, session=True, 
+      domain='default'):
     """Generate a URL at which the user will login and be redirected back.
 
     Users enter their credentials on a Google login page and a token is sent
     to the URL specified in next. See documentation for AuthSub login at:
-    http://code.google.com/apis/accounts/AuthForWebApps.html
+    http://code.google.com/apis/accounts/docs/AuthSub.html
 
     Args:
       next: string The URL user will be sent to after logging in.
-      scope: string The URL of the service to be accessed.
+      scope: string or list of strings. The URLs of the services to be 
+             accessed.
       secure: boolean (optional) Determines whether or not the issued token
               is a secure token.
       session: boolean (optional) Determines whether or not the issued token
                can be upgraded to a session token.
     """
+    if not isinstance(scope, (list, tuple)):
+      scope = (scope,)
+    return gdata.auth.generate_auth_sub_url(next, scope, secure=secure, 
+        session=session, 
+        request_url='%s/accounts/AuthSubRequest' % AUTH_SERVER_HOST, 
+        domain=domain)
 
-    # Translate True/False values for parameters into numeric values acceoted
-    # by the AuthSub service.
-    if secure:
-      secure = 1
-    else:
-      secure = 0
-
-    if session:
-      session = 1
-    else:
-      session = 0
-
-    request_params = urllib.urlencode({'next': next, 'scope': scope,
-                                    'secure': secure, 'session': session})
-    return '%s/accounts/AuthSubRequest?%s' % (AUTH_SERVER_HOST, request_params)
-
-  def UpgradeToSessionToken(self):
+  def UpgradeToSessionToken(self, token=None):
     """Upgrades a single use AuthSub token to a session token.
 
+    Args:
+      token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken
+             (optional) which is good for a single use but can be upgraded
+             to a session token. If no token is passed in, the token
+             is found by looking in the token_store by looking for a token
+             for the current scope.
+
     Raises:
       NonAuthSubToken if the user's auth token is not an AuthSub token
+      TokenUpgradeFailed if the server responded to the request with an 
+      error.
     """
-   
-    if not self.__auth_token.startswith(AUTHSUB_AUTH_LABEL):
+    if token is None:
+      scopes = lookup_scopes(self.service)
+      if scopes:
+        token = self.token_store.find_token(scopes[0])
+      else:
+        token = self.token_store.find_token(atom.token_store.SCOPE_ALL)
+    if not isinstance(token, gdata.auth.AuthSubToken):
       raise NonAuthSubToken
 
-    response = self.handler.HttpRequest(self, 'GET', None, 
-        AUTH_SERVER_HOST + '/accounts/AuthSubSessionToken', 
-        extra_headers={'Authorization':self.__auth_token}, 
-        content_type='application/x-www-form-urlencoded')
+    self.SetAuthSubToken(self.upgrade_to_session_token(token))
 
+  def upgrade_to_session_token(self, token):
+    """Upgrades a single use AuthSub token to a session token.
+
+    Args:
+      token: A gdata.auth.AuthSubToken or gdata.auth.SecureAuthSubToken
+             which is good for a single use but can be upgraded to a
+             session token.
+
+    Returns:
+      The upgraded token as a gdata.auth.AuthSubToken object.
+
+    Raises:
+      TokenUpgradeFailed if the server responded to the request with an 
+      error.
+    """
+    response = token.perform_request(self.http_client, 'GET', 
+        AUTH_SERVER_HOST + '/accounts/AuthSubSessionToken', 
+        headers={'Content-Type':'application/x-www-form-urlencoded'})
     response_body = response.read()
     if response.status == 200:
-      for response_line in response_body.splitlines():
-        if response_line.startswith('Token='):
-          self.SetAuthSubToken(response_line.lstrip('Token='))
+      token.set_token_string(
+          gdata.auth.token_from_http_body(response_body))
+      return token
+    else:
+      raise TokenUpgradeFailed({'status': response.status,
+                                'reason': 'Non 200 response on upgrade',
+                                'body': response_body})
 
   def RevokeAuthSubToken(self):
     """Revokes an existing AuthSub token.
@@ -444,16 +895,37 @@
     Raises:
       NonAuthSubToken if the user's auth token is not an AuthSub token
     """
-
-    if not self.__auth_token.startswith(AUTHSUB_AUTH_LABEL):
+    scopes = lookup_scopes(self.service)
+    token = self.token_store.find_token(scopes[0])
+    if not isinstance(token, gdata.auth.AuthSubToken):
       raise NonAuthSubToken
-    
-    response = self.handler.HttpRequest(self, 'GET', None, 
+
+    response = token.perform_request(self.http_client, 'GET', 
         AUTH_SERVER_HOST + '/accounts/AuthSubRevokeToken', 
-        extra_headers={'Authorization':self.__auth_token}, 
-        content_type='application/x-www-form-urlencoded')
+        headers={'Content-Type':'application/x-www-form-urlencoded'})
+    if response.status == 200:
+      self.token_store.remove_token(token)
+
+  def AuthSubTokenInfo(self):
+    """Fetches the AuthSub token's metadata from the server.
+
+    Raises:
+      NonAuthSubToken if the user's auth token is not an AuthSub token
+    """
+    scopes = lookup_scopes(self.service)
+    token = self.token_store.find_token(scopes[0])
+    if not isinstance(token, gdata.auth.AuthSubToken):
+      raise NonAuthSubToken
+
+    response = token.perform_request(self.http_client, 'GET', 
+        AUTH_SERVER_HOST + '/accounts/AuthSubTokenInfo', 
+        headers={'Content-Type':'application/x-www-form-urlencoded'})
+    result_body = response.read()
     if response.status == 200:
-      self.__auth_token = None
+      return result_body
+    else:
+      raise RequestError, {'status': response.status,
+          'body': result_body}
 
   # CRUD operations
   def Get(self, uri, extra_headers=None, redirects_remaining=4, 
@@ -498,10 +970,6 @@
     if extra_headers is None:
       extra_headers = {}
 
-    # Add the authentication header to the Get request
-    if self.__auth_token:
-      extra_headers['Authorization'] = self.__auth_token
-
     if self.__gsessionid is not None:
       if uri.find('gsessionid=') < 0:
         if uri.find('?') > -1:
@@ -509,8 +977,8 @@
         else:
           uri += '?gsessionid=%s' % (self.__gsessionid,)
 
-    server_response = self.handler.HttpRequest(self, 'GET', None, uri, 
-        extra_headers=extra_headers)
+    server_response = self.request('GET', uri, 
+        headers=extra_headers)
     result_body = server_response.read()
 
     if server_response.status == 200:
@@ -536,7 +1004,7 @@
           m = re.compile('[\?\&]gsessionid=(\w*)').search(location)
           if m is not None:
             self.__gsessionid = m.group(1)
-          return self.Get(location, extra_headers, redirects_remaining - 1, 
+          return GDataService.Get(self, location, extra_headers, redirects_remaining - 1, 
               encoding=encoding, converter=converter)
         else:
           raise RequestError, {'status': server_response.status,
@@ -554,8 +1022,8 @@
     """Returns a MediaSource containing media and its metadata from the given
     URI string.
     """
-    response_handle = self.handler.HttpRequest(self, 'GET', None, uri, 
-        extra_headers=extra_headers)
+    response_handle = self.request('GET', uri,
+        headers=extra_headers)
     return gdata.MediaSource(response_handle, response_handle.getheader(
             'Content-Type'),
         response_handle.getheader('Content-Length'))
@@ -578,7 +1046,8 @@
       A GDataEntry built from the XML in the server's response.
     """
 
-    result = self.Get(uri, extra_headers, converter=atom.EntryFromString)
+    result = GDataService.Get(self, uri, extra_headers, 
+        converter=atom.EntryFromString)
     if isinstance(result, atom.Entry):
       return result
     else:
@@ -603,7 +1072,7 @@
       A GDataFeed built from the XML in the server's response.
     """
 
-    result = self.Get(uri, extra_headers, converter=converter)
+    result = GDataService.Get(self, uri, extra_headers, converter=converter)
     if isinstance(result, atom.Feed):
       return result
     else:
@@ -633,7 +1102,8 @@
     # Make a GET request on the next link and use the above closure for the
     # converted which processes the XML string from the server.
     if next_link and next_link.href:
-      return self.Get(next_link.href, converter=ConvertToFeedClass)
+      return GDataService.Get(self, next_link.href, 
+          converter=ConvertToFeedClass)
     else:
       return None
 
@@ -671,9 +1141,9 @@
       or the results of running converter on the server's result body (if
       converter was specified).
     """
-    return self.PostOrPut('POST', data, uri, extra_headers=extra_headers, 
-        url_params=url_params, escape_params=escape_params, 
-        redirects_remaining=redirects_remaining, 
+    return GDataService.PostOrPut(self, 'POST', data, uri, 
+        extra_headers=extra_headers, url_params=url_params, 
+        escape_params=escape_params, redirects_remaining=redirects_remaining,
         media_source=media_source, converter=converter)
 
   def PostOrPut(self, verb, data, uri, extra_headers=None, url_params=None, 
@@ -714,10 +1184,6 @@
     if extra_headers is None:
       extra_headers = {}
 
-    # Add the authentication header to the Get request
-    if self.__auth_token:
-      extra_headers['Authorization'] = self.__auth_token
-
     if self.__gsessionid is not None:
       if uri.find('gsessionid=') < 0:
         if uri.find('?') > -1:
@@ -743,31 +1209,27 @@
           len(multipart[1]) + len(multipart[2]) +
           len(data_str) + media_source.content_length)
 
-      server_response = self.handler.HttpRequest(self, verb, 
-          [multipart[0], data_str, multipart[1], media_source.file_handle,
-              multipart[2]], uri,
-          extra_headers=extra_headers, url_params=url_params, 
-          escape_params=escape_params, 
-          content_type='multipart/related; boundary=END_OF_PART')
+      extra_headers['Content-Type'] = 'multipart/related; boundary=END_OF_PART'
+      server_response = self.request(verb, uri, 
+          data=[multipart[0], data_str, multipart[1], media_source.file_handle,
+              multipart[2]], headers=extra_headers)
       result_body = server_response.read()
       
     elif media_source or isinstance(data, gdata.MediaSource):
       if isinstance(data, gdata.MediaSource):
         media_source = data
-      extra_headers['Content-Length'] = media_source.content_length
-      server_response = self.handler.HttpRequest(self, verb, 
-          media_source.file_handle, uri, extra_headers=extra_headers, 
-          url_params=url_params, escape_params=escape_params, 
-          content_type=media_source.content_type)
+      extra_headers['Content-Length'] = str(media_source.content_length)
+      extra_headers['Content-Type'] = media_source.content_type
+      server_response = self.request(verb, uri, 
+          data=media_source.file_handle, headers=extra_headers)
       result_body = server_response.read()
 
     else:
       http_data = data
       content_type = 'application/atom+xml'
-      server_response = self.handler.HttpRequest(self, verb, 
-          http_data, uri, extra_headers=extra_headers, 
-          url_params=url_params, escape_params=escape_params, 
-          content_type=content_type)
+      extra_headers['Content-Type'] = content_type
+      server_response = self.request(verb, uri, data=http_data,
+          headers=extra_headers)
       result_body = server_response.read()
 
     # Server returns 201 for most post requests, but when performing a batch
@@ -789,9 +1251,9 @@
           m = re.compile('[\?\&]gsessionid=(\w*)').search(location)
           if m is not None:
             self.__gsessionid = m.group(1) 
-          return self.Post(data, location, extra_headers, url_params,
-              escape_params, redirects_remaining - 1, media_source, 
-              converter=converter)
+          return GDataService.PostOrPut(self, verb, data, location, 
+              extra_headers, url_params, escape_params, 
+              redirects_remaining - 1, media_source, converter=converter)
         else:
           raise RequestError, {'status': server_response.status,
               'reason': '302 received without Location header',
@@ -836,9 +1298,9 @@
       or the results of running converter on the server's result body (if
       converter was specified).
     """
-    return self.PostOrPut('PUT', data, uri, extra_headers=extra_headers, 
-        url_params=url_params, escape_params=escape_params, 
-        redirects_remaining=redirects_remaining, 
+    return GDataService.PostOrPut(self, 'PUT', data, uri, 
+        extra_headers=extra_headers, url_params=url_params, 
+        escape_params=escape_params, redirects_remaining=redirects_remaining,
         media_source=media_source, converter=converter)
 
   def Delete(self, uri, extra_headers=None, url_params=None, 
@@ -867,20 +1329,15 @@
     if extra_headers is None:
       extra_headers = {}
 
-    # Add the authentication header to the Get request
-    if self.__auth_token:
-      extra_headers['Authorization'] = self.__auth_token
-
     if self.__gsessionid is not None:
       if uri.find('gsessionid=') < 0:
         if uri.find('?') > -1:
           uri += '&gsessionid=%s' % (self.__gsessionid,)
         else:
           uri += '?gsessionid=%s' % (self.__gsessionid,)
-  
-    server_response = self.handler.HttpRequest(self, 'DELETE', None, uri,
-        extra_headers=extra_headers, url_params=url_params, 
-        escape_params=escape_params)
+ 
+    server_response = self.request('DELETE', uri, 
+        headers=extra_headers)
     result_body = server_response.read()
 
     if server_response.status == 200:
@@ -892,8 +1349,8 @@
           m = re.compile('[\?\&]gsessionid=(\w*)').search(location)
           if m is not None:
             self.__gsessionid = m.group(1) 
-          return self.Delete(location, extra_headers, url_params,
-              escape_params, redirects_remaining - 1)
+          return GDataService.Delete(self, location, extra_headers, 
+              url_params, escape_params, redirects_remaining - 1)
         else:
           raise RequestError, {'status': server_response.status,
               'reason': '302 received without Location header',
@@ -907,6 +1364,86 @@
           'reason': server_response.reason, 'body': result_body}
 
 
+def ExtractToken(url, scopes_included_in_next=True):
+  """Gets the AuthSub token from the current page's URL.
+
+  Designed to be used on the URL that the browser is sent to after the user
+  authorizes this application at the page given by GenerateAuthSubRequestUrl.
+
+  Args:
+    url: The current page's URL. It should contain the token as a URL
+        parameter. Example: 'http://example.com/?...&token=abcd435'
+    scopes_included_in_next: If True, this function looks for a scope value
+        associated with the token. The scope is a URL parameter with the
+        key set to SCOPE_URL_PARAM_NAME. This parameter should be present
+        if the AuthSub request URL was generated using
+        GenerateAuthSubRequestUrl with include_scope_in_next set to True.
+
+  Returns:
+    A tuple containing the token string and a list of scope strings for which
+    this token should be valid. If the scope was not included in the URL, the
+    tuple will contain (token, None).
+  """
+  parsed = urlparse.urlparse(url)
+  token = gdata.auth.AuthSubTokenFromUrl(parsed[4])
+  scopes = ''
+  if scopes_included_in_next:
+    for pair in parsed[4].split('&'):
+      if pair.startswith('%s=' % SCOPE_URL_PARAM_NAME):
+        scopes = urllib.unquote_plus(pair.split('=')[1])
+  return (token, scopes.split(' '))
+
+
+def GenerateAuthSubRequestUrl(next, scopes, hd='default', secure=False,
+    session=True, request_url='http://www.google.com/accounts/AuthSubRequest',
+    include_scopes_in_next=True):
+  """Creates a URL to request an AuthSub token to access Google services.
+
+  For more details on AuthSub, see the documentation here:
+  http://code.google.com/apis/accounts/docs/AuthSub.html
+
+  Args:
+    next: The URL where the browser should be sent after the user authorizes
+        the application. This page is responsible for receiving the token
+        which is embeded in the URL as a parameter.
+    scopes: The base URL to which access will be granted. Example:
+        'http://www.google.com/calendar/feeds' will grant access to all
+        URLs in the Google Calendar data API. If you would like a token for
+        multiple scopes, pass in a list of URL strings.
+    hd: The domain to which the user's account belongs. This is set to the
+        domain name if you are using Google Apps. Example: 'example.org'
+        Defaults to 'default'
+    secure: If set to True, all requests should be signed. The default is
+        False.
+    session: If set to True, the token received by the 'next' URL can be
+        upgraded to a multiuse session token. If session is set to False, the
+        token may only be used once and cannot be upgraded. Default is True.
+    request_url: The base of the URL to which the user will be sent to
+        authorize this application to access their data. The default is
+        'http://www.google.com/accounts/AuthSubRequest'.
+    include_scopes_in_next: Boolean if set to true, the 'next' parameter will
+        be modified to include the requested scope as a URL parameter. The
+        key for the next's scope parameter will be SCOPE_URL_PARAM_NAME. The
+        benefit of including the scope URL as a parameter to the next URL, is
+        that the page which receives the AuthSub token will be able to tell
+        which URLs the token grants access to.
+
+  Returns:
+    A URL string to which the browser should be sent.
+  """
+  if isinstance(scopes, list):
+    scope = ' '.join(scopes)
+  else:
+    scope = scopes
+  if include_scopes_in_next:
+    if next.find('?') > -1:
+      next += '&%s' % urllib.urlencode({SCOPE_URL_PARAM_NAME:scope})
+    else:
+      next += '?%s' % urllib.urlencode({SCOPE_URL_PARAM_NAME:scope})
+  return gdata.auth.GenerateAuthSubUrl(next=next, scope=scope, secure=secure,
+      session=session, request_url=request_url, domain=hd)
+
+
 class Query(dict):
   """Constructs a query URL to be used in GET requests
   

Modified: trunk/conduit/modules/GoogleModule/gdata/spreadsheet/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/spreadsheet/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/spreadsheet/service.py	Tue Mar 17 09:00:35 2009
@@ -48,13 +48,24 @@
   """Client for the Google Spreadsheets service."""
 
   def __init__(self, email=None, password=None, source=None,
-               server='spreadsheets.google.com',
-               additional_headers=None):
-    gdata.service.GDataService.__init__(self, email=email, password=password,
-                                        service='wise', source=source,
-                                        server=server,
-                                        additional_headers=additional_headers)
-                                        
+               server='spreadsheets.google.com', additional_headers=None,
+               **kwargs):
+    """Creates a client for the Google Spreadsheets service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'spreadsheets.google.com'.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='wise', source=source,
+        server=server, additional_headers=additional_headers, **kwargs)
+
   def GetSpreadsheetsFeed(self, key=None, query=None, visibility='private', 
       projection='full'):
     """Gets a spreadsheets feed or a specific entry if a key is defined

Modified: trunk/conduit/modules/GoogleModule/gdata/spreadsheet/text_db.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/spreadsheet/text_db.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/spreadsheet/text_db.py	Tue Mar 17 09:00:35 2009
@@ -247,10 +247,13 @@
           spreadsheet_key=self.spreadsheet_key)]
     else:
       matching_tables = []
-      title_query = gdata.spreadsheet.service.DocumentQuery()
-      title_query.title = name
+      query = None
+      if name:
+        query = gdata.spreadsheet.service.DocumentQuery()
+        query.title = name
+ 
       worksheet_feed = self.client._GetSpreadsheetsClient().GetWorksheetsFeed(
-          self.spreadsheet_key, query=title_query)
+          self.spreadsheet_key, query=query)
       for entry in worksheet_feed.entry:
         matching_tables.append(Table(name=entry.title.text, 
             worksheet_entry=entry, database_client=self.client, 

Modified: trunk/conduit/modules/GoogleModule/gdata/test_data.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/test_data.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/test_data.py	Tue Mar 17 09:00:35 2009
@@ -1217,15 +1217,15 @@
 <atom:entry xmlns:atom="http://www.w3.org/2005/Atom";
   xmlns:apps="http://schemas.google.com/apps/2006";
   xmlns:gd="http://schemas.google.com/g/2005";>
-  <atom:id>https://www.google.com/a/feeds/example.com/nickname/2.0/Foo</atom:id>
+  <atom:id>https://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo</atom:id>
   <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
   <atom:category scheme='http://schemas.google.com/g/2005#kind'
     term='http://schemas.google.com/apps/2006#nickname'/>
   <atom:title type="text">Foo</atom:title>
   <atom:link rel="self" type="application/atom+xml"
-    href="https://www.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
+    href="https://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
   <atom:link rel="edit" type="application/atom+xml"
-    href="https://www.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
+    href="https://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
   <apps:nickname name="Foo"/>
   <apps:login userName="TestUser"/>
 </atom:entry>"""
@@ -1235,7 +1235,7 @@
   xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/";
   xmlns:apps="http://schemas.google.com/apps/2006";>
   <atom:id>
-    http://www.google.com/a/feeds/example.com/nickname/2.0
+    http://apps-apis.google.com/a/feeds/example.com/nickname/2.0
   </atom:id>
   <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
   <atom:category scheme='http://schemas.google.com/g/2005#kind'
@@ -1243,39 +1243,39 @@
   <atom:title type="text">Nicknames for user SusanJones</atom:title>
   <atom:link rel='http://schemas.google.com/g/2005#feed'
     type="application/atom+xml"
-    href="http://www.google.com/a/feeds/example.com/nickname/2.0"/>
+    href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0"/>
   <atom:link rel='http://schemas.google.com/g/2005#post'
     type="application/atom+xml"
-    href="http://www.google.com/a/feeds/example.com/nickname/2.0"/>
+    href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0"/>
   <atom:link rel="self" type="application/atom+xml"
-    href="http://www.google.com/a/feeds/example.com/nickname/2.0?username=TestUser"/>
+    href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0?username=TestUser"/>
   <openSearch:startIndex>1</openSearch:startIndex>
   <openSearch:itemsPerPage>2</openSearch:itemsPerPage>
   <atom:entry>
     <atom:id>
-      http://www.google.com/a/feeds/example.com/nickname/2.0/Foo
+      http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo
     </atom:id>
     <atom:category scheme='http://schemas.google.com/g/2005#kind'
       term='http://schemas.google.com/apps/2006#nickname'/>
     <atom:title type="text">Foo</atom:title>
     <atom:link rel="self" type="application/atom+xml"
-      href="http://www.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
+      href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
     <atom:link rel="edit" type="application/atom+xml"
-      href="http://www.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
+      href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Foo"/>
     <apps:nickname name="Foo"/>
     <apps:login userName="TestUser"/>
   </atom:entry>
   <atom:entry>
     <atom:id>
-      http://www.google.com/a/feeds/example.com/nickname/2.0/suse
+      http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/suse
     </atom:id>
     <atom:category scheme='http://schemas.google.com/g/2005#kind'
       term='http://schemas.google.com/apps/2006#nickname'/>
     <atom:title type="text">suse</atom:title>
     <atom:link rel="self" type="application/atom+xml"
-      href="http://www.google.com/a/feeds/example.com/nickname/2.0/Bar"/>
+      href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Bar"/>
     <atom:link rel="edit" type="application/atom+xml"
-      href="http://www.google.com/a/feeds/example.com/nickname/2.0/Bar"/>
+      href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0/Bar"/>
     <apps:nickname name="Bar"/>
     <apps:login userName="TestUser"/>
   </atom:entry>
@@ -1285,23 +1285,23 @@
 <atom:entry xmlns:atom="http://www.w3.org/2005/Atom";
             xmlns:apps="http://schemas.google.com/apps/2006";
             xmlns:gd="http://schemas.google.com/g/2005";>
-  <atom:id>https://www.google.com/a/feeds/example.com/user/2.0/TestUser</atom:id>
+  <atom:id>https://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser</atom:id>
   <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
   <atom:category scheme='http://schemas.google.com/g/2005#kind'
     term='http://schemas.google.com/apps/2006#user'/>
   <atom:title type="text">TestUser</atom:title>
   <atom:link rel="self" type="application/atom+xml"
-    href="https://www.google.com/a/feeds/example.com/user/2.0/TestUser"/>
+    href="https://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser"/>
   <atom:link rel="edit" type="application/atom+xml"
-    href="https://www.google.com/a/feeds/example.com/user/2.0/TestUser"/>
+    href="https://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser"/>
   <apps:login userName="TestUser" password="password" suspended="false"
     ipWhitelisted='false' hashFunctionName="SHA-1"/>
   <apps:name familyName="Test" givenName="User"/>
   <apps:quota limit="1024"/>
   <gd:feedLink rel='http://schemas.google.com/apps/2006#user.nicknames'
-    href="https://www.google.com/a/feeds/example.com/nickname/2.0?username=Test-3121"/>
+    href="https://apps-apis.google.com/a/feeds/example.com/nickname/2.0?username=Test-3121"/>
   <gd:feedLink rel='http://schemas.google.com/apps/2006#user.emailLists'
-    href="https://www.google.com/a/feeds/example.com/emailList/2 0?recipient=testlist example com"/>
+    href="https://apps-apis.google.com/a/feeds/example.com/emailList/2 0?recipient=testlist example com"/>
 </atom:entry>"""
 
 USER_FEED = """<?xml version="1.0" encoding="UTF-8"?>
@@ -1310,64 +1310,64 @@
   xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/";
   xmlns:gd="http://schemas.google.com/g/2005";>
     <atom:id>
-        http://www.google.com/a/feeds/example.com/user/2.0
+        http://apps-apis.google.com/a/feeds/example.com/user/2.0
     </atom:id>
     <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
     <atom:category scheme='http://schemas.google.com/g/2005#kind' 
         term='http://schemas.google.com/apps/2006#user'/>
     <atom:title type="text">Users</atom:title>
     <atom:link rel="next" type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/user/2.0?startUsername=john"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/user/2.0?startUsername=john"/>
     <atom:link rel='http://schemas.google.com/g/2005#feed' 
         type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/user/2.0"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/user/2.0"/>
     <atom:link rel='http://schemas.google.com/g/2005#post'
         type="application/atom+xml"
-        href="http://www.google.com/a/feeds/example.com/user/2.0"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/user/2.0"/>
     <atom:link rel="self" type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/user/2.0"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/user/2.0"/>
     <openSearch:startIndex>1</openSearch:startIndex>
     <atom:entry>
         <atom:id>
-            http://www.google.com/a/feeds/example.com/user/2.0/TestUser
+            http://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser
         </atom:id>
         <atom:category scheme='http://schemas.google.com/g/2005#kind'
             term='http://schemas.google.com/apps/2006#user'/>
         <atom:title type="text">TestUser</atom:title>
         <atom:link rel="self" type="application/atom+xml" 
-            href="http://www.google.com/a/feeds/example.com/user/2.0/TestUser"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser"/>
         <atom:link rel="edit" type="application/atom+xml"
-            href="http://www.google.com/a/feeds/example.com/user/2.0/TestUser"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/user/2.0/TestUser"/>
         <gd:who rel='http://schemas.google.com/apps/2006#user.recipient' 
             email="TestUser example com"/>
         <apps:login userName="TestUser" suspended="false"/>
         <apps:quota limit="2048"/>
         <apps:name familyName="Test" givenName="User"/>
         <gd:feedLink rel='http://schemas.google.com/apps/2006#user.nicknames'
-            href="http://www.google.com/a/feeds/example.com/nickname/2.0?username=TestUser"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0?username=TestUser"/>
         <gd:feedLink rel='http://schemas.google.com/apps/2006#user.emailLists'
-            href="http://www.google.com/a/feeds/example.com/emailList/2 0?recipient=TestUser example com"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2 0?recipient=TestUser example com"/>
     </atom:entry>
     <atom:entry>
         <atom:id>
-            http://www.google.com/a/feeds/example.com/user/2.0/JohnSmith
+            http://apps-apis.google.com/a/feeds/example.com/user/2.0/JohnSmith
         </atom:id>
         <atom:category scheme='http://schemas.google.com/g/2005#kind'
             term='http://schemas.google.com/apps/2006#user'/>
         <atom:title type="text">JohnSmith</atom:title>
         <atom:link rel="self" type="application/atom+xml" 
-            href="http://www.google.com/a/feeds/example.com/user/2.0/JohnSmith"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/user/2.0/JohnSmith"/>
         <atom:link rel="edit" type="application/atom+xml"
-            href="http://www.google.com/a/feeds/example.com/user/2.0/JohnSmith"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/user/2.0/JohnSmith"/>
         <gd:who rel='http://schemas.google.com/apps/2006#user.recipient'
             email="JohnSmith example com"/>
         <apps:login userName="JohnSmith" suspended="false"/>
         <apps:quota limit="2048"/>
         <apps:name familyName="Smith" givenName="John"/>
         <gd:feedLink rel='http://schemas.google.com/apps/2006#user.nicknames'
-            href="http://www.google.com/a/feeds/example.com/nickname/2.0?username=JohnSmith"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/nickname/2.0?username=JohnSmith"/>
         <gd:feedLink rel='http://schemas.google.com/apps/2006#user.emailLists'
-            href="http://www.google.com/a/feeds/example.com/emailList/2 0?recipient=JohnSmith example com"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2 0?recipient=JohnSmith example com"/>
     </atom:entry>
 </atom:feed>"""
 
@@ -1376,19 +1376,19 @@
   xmlns:apps="http://schemas.google.com/apps/2006";
   xmlns:gd="http://schemas.google.com/g/2005";>
     <atom:id>
-      https://www.google.com/a/feeds/example.com/emailList/2.0/testlist
+      https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/testlist
     </atom:id>
     <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
     <atom:category scheme='http://schemas.google.com/g/2005#kind'
       term='http://schemas.google.com/apps/2006#emailList'/>
     <atom:title type="text">testlist</atom:title>
     <atom:link rel="self" type="application/atom+xml" 
-      href="https://www.google.com/a/feeds/example.com/emailList/2.0/testlist"/>
+      href="https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/testlist"/>
     <atom:link rel="edit" type="application/atom+xml" 
-      href="https://www.google.com/a/feeds/example.com/emailList/2.0/testlist"/>
+      href="https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/testlist"/>
     <apps:emailList name="testlist"/>
     <gd:feedLink rel='http://schemas.google.com/apps/2006#emailList.recipients'
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0/testlist/recipient/"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/testlist/recipient/"/>
 </atom:entry>"""
 
 EMAIL_LIST_FEED = """<?xml version="1.0" encoding="UTF-8"?>
@@ -1397,54 +1397,54 @@
   xmlns:apps="http://schemas.google.com/apps/2006";
   xmlns:gd="http://schemas.google.com/g/2005";>
     <atom:id>
-        http://www.google.com/a/feeds/example.com/emailList/2.0
+        http://apps-apis.google.com/a/feeds/example.com/emailList/2.0
     </atom:id>
     <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
     <atom:category scheme='http://schemas.google.com/g/2005#kind'
         term='http://schemas.google.com/apps/2006#emailList'/>
     <atom:title type="text">EmailLists</atom:title>
     <atom:link rel="next" type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0?startEmailListName=john"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0?startEmailListName=john"/>
     <atom:link rel='http://schemas.google.com/g/2005#feed'
         type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0"/>
     <atom:link rel='http://schemas.google.com/g/2005#post' 
         type="application/atom+xml"
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0"/>
     <atom:link rel="self" type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0"/>
     <openSearch:startIndex>1</openSearch:startIndex>
     <atom:entry>
         <atom:id>
-            http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales
+            http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales
         </atom:id>
         <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
         <atom:category scheme='http://schemas.google.com/g/2005#kind'
             term='http://schemas.google.com/apps/2006#emailList'/>
         <atom:title type="text">us-sales</atom:title>
         <atom:link rel="self" type="application/atom+xml" 
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales"/>
         <atom:link rel="edit" type="application/atom+xml"
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales"/>
         <apps:emailList name="us-sales"/>
         <gd:feedLink rel='http://schemas.google.com/apps/2006#emailList.recipients'
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/"/>
     </atom:entry>
     <atom:entry>
         <atom:id>
-            http://www.google.com/a/feeds/example.com/emailList/2.0/us-eng
+            http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-eng
         </atom:id>
         <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
         <atom:category scheme='http://schemas.google.com/g/2005#kind'
             term='http://schemas.google.com/apps/2006#emailList'/>
         <atom:title type="text">us-eng</atom:title>
         <atom:link rel="self" type="application/atom+xml" 
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-eng"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-eng"/>
         <atom:link rel="edit" type="application/atom+xml"
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-eng"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-eng"/>
         <apps:emailList name="us-eng"/>
         <gd:feedLink rel='http://schemas.google.com/apps/2006#emailList.recipients'
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-eng/recipient/"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-eng/recipient/"/>
     </atom:entry>
 </atom:feed>"""
 
@@ -1452,15 +1452,15 @@
 <atom:entry xmlns:atom="http://www.w3.org/2005/Atom";
   xmlns:apps="http://schemas.google.com/apps/2006";
   xmlns:gd="http://schemas.google.com/g/2005";>
-    <atom:id>https://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com</atom:id>
+    <atom:id>https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com</atom:id>
     <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
     <atom:category scheme='http://schemas.google.com/g/2005#kind'
         term='http://schemas.google.com/apps/2006#emailList.recipient'/>
     <atom:title type="text">TestUser</atom:title>
     <atom:link rel="self" type="application/atom+xml" 
-        href="https://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com"/>
+        href="https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com"/>
     <atom:link rel="edit" type="application/atom+xml" 
-        href="https://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com"/>
+        href="https://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/TestUser%40example.com"/>
     <gd:who email="TestUser example com"/>
 </atom:entry>"""
 
@@ -1469,49 +1469,49 @@
   xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/";
   xmlns:gd="http://schemas.google.com/g/2005";>
     <atom:id>
-        http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient
+        http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient
     </atom:id>
     <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
     <atom:category scheme='http://schemas.google.com/g/2005#kind'
         term='http://schemas.google.com/apps/2006#emailList.recipient'/>
     <atom:title type="text">Recipients for email list us-sales</atom:title>
     <atom:link rel="next" type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/?startRecipient=terry example com"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/?startRecipient=terry example com"/>
     <atom:link rel='http://schemas.google.com/g/2005#feed'
         type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient"/>
     <atom:link rel='http://schemas.google.com/g/2005#post'
         type="application/atom+xml"
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient"/>
     <atom:link rel="self" type="application/atom+xml" 
-        href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient"/>
+        href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient"/>
     <openSearch:startIndex>1</openSearch:startIndex>
     <atom:entry>
         <atom:id>
-            http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/joe%40example.com
+            http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/joe%40example.com
         </atom:id>
         <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
         <atom:category scheme='http://schemas.google.com/g/2005#kind'
             term='http://schemas.google.com/apps/2006#emailList.recipient'/>
         <atom:title type="text">joe example com</atom:title>
         <atom:link rel="self" type="application/atom+xml" 
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/joe%40example.com"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/joe%40example.com"/>
         <atom:link rel="edit" type="application/atom+xml"
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/joe%40example.com"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/joe%40example.com"/>
         <gd:who email="joe example com"/>
     </atom:entry>
     <atom:entry>
         <atom:id>
-            http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/susan%40example.com
+            http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/susan%40example.com
         </atom:id>
         <atom:updated>1970-01-01T00:00:00.000Z</atom:updated>
         <atom:category scheme='http://schemas.google.com/g/2005#kind'
             term='http://schemas.google.com/apps/2006#emailList.recipient'/>
         <atom:title type="text">susan example com</atom:title>
         <atom:link rel="self" type="application/atom+xml" 
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/susan%40example.com"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/susan%40example.com"/>
         <atom:link rel="edit" type="application/atom+xml"
-            href="http://www.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/susan%40example.com"/>
+            href="http://apps-apis.google.com/a/feeds/example.com/emailList/2.0/us-sales/recipient/susan%40example.com"/>
         <gd:who email="susan example com"/>
     </atom:entry>
 </atom:feed>"""
@@ -2075,7 +2075,7 @@
 </feed>"""
 
 YOUTUBE_VIDEO_FEED = """<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' xmlns:media='http://search.yahoo.com/mrss/' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:gd='http://schemas.google.com/g/2005'><id>http://gdata.youtube.com/feeds/api/standardfeeds/top_rated</id><updated>2008-05-14T02:24:07.000-07:00</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><title type='text'>Top Rated</title><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='alternate' type='text/html' href='http://www.youtube.com/browse?s=tr'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated'/><link rel='self' type='application/atom+xml' href='ht
 tp://gdata.youtube.com/feeds/api/standardfeeds/top_rated?start-index=1&amp;max-results=25'/><link rel='next' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?start-index=26&amp;max-results=25'/><author><name>YouTube</name><uri>http://www.youtube.com/</uri></author><generator version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator><openSearch:totalResults>100</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage>
-<entry><id>http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8</id><published>2008-03-20T10:17:27.000-07:00</published><updated>2008-05-14T04:26:37.000-07:00</updated><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='karyn'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='garcia'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='me'/><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='boyfriend'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='por'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='te'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='odeio'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='amar'/><category scheme='http://gdata.youtube.com/
 schemas/2007/categories.cat' term='Music' label='Music'/><title type='text'>Me odeio por te amar - KARYN GARCIA</title><content type='text'>http://www.karyngarcia.com.br</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=C71ypXYGho8'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8/related'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated/C71ypXYGho8'/><author><name>TvKarynGarcia</name><uri>http://gdata.youtube.com/feeds/api/users/tvkaryngarcia</uri></author><media:group><media:title type='plain'>Me odeio por te amar - KARYN GARCIA</media:title><media:description type='plain'>http://www.karyngarcia.com.br</media:description><media:keywords>amar, boyfriend, garcia, karyn, me, odeio, por, te</media:keywords><yt:duration seconds='203'/><media:category label='Music' scheme='http://g
 data.youtube.com/schemas/2007/categories.cat'>Music</media:category><media:content url='http://www.youtube.com/v/C71ypXYGho8' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='203' yt:format='5'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='203' yt:format='1'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='203' yt:format='6'/><media:player url='http://www.youtube.com/watch?v=C71ypXYGho8'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/2.jpg' height='97' width='130' time='00:01:41.500'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/1.jpg' height='97' width='130' time='00:00:50.750'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/3.jpg' height='97' width='130'
  time='00:02:32.250'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/0.jpg' height='240' width='320' time='00:01:41.500'/></media:group><yt:statistics viewCount='138864' favoriteCount='2474'/><gd:rating min='1' max='5' numRaters='4626' average='4.95'/><gd:comments><gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8/comments' countHint='27'/></gd:comments></entry>
+<entry><id>http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8</id><published>2008-03-20T10:17:27.000-07:00</published><updated>2008-05-14T04:26:37.000-07:00</updated><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='karyn'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='garcia'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='me'/><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='boyfriend'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='por'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='te'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='odeio'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='amar'/><category scheme='http://gdata.youtube.com/
 schemas/2007/categories.cat' term='Music' label='Music'/><title type='text'>Me odeio por te amar - KARYN GARCIA</title><content type='text'>http://www.karyngarcia.com.br</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=C71ypXYGho8'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8/related'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated/C71ypXYGho8'/><author><name>TvKarynGarcia</name><uri>http://gdata.youtube.com/feeds/api/users/tvkaryngarcia</uri></author><media:group><media:title type='plain'>Me odeio por te amar - KARYN GARCIA</media:title><media:description type='plain'>http://www.karyngarcia.com.br</media:description><media:keywords>amar, boyfriend, garcia, karyn, me, odeio, por, te</media:keywords><yt:duration seconds='203'/><media:category label='Music' scheme='http://g
 data.youtube.com/schemas/2007/categories.cat'>Music</media:category><media:category label='test111' scheme='http://gdata.youtube.com/schemas/2007/developertags.cat'>test111</media:category><media:category label='test222' scheme='http://gdata.youtube.com/schemas/2007/developertags.cat'>test222</media:category><media:content url='http://www.youtube.com/v/C71ypXYGho8' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='203' yt:format='5'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='203' yt:format='1'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQmPhgZ2pXK9CxMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='203' yt:format='6'/><media:player url='http://www.youtube.com/watch?v=C71ypXYGho8'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/2.jpg' heigh
 t='97' width='130' time='00:01:41.500'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/1.jpg' height='97' width='130' time='00:00:50.750'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/3.jpg' height='97' width='130' time='00:02:32.250'/><media:thumbnail url='http://img.youtube.com/vi/C71ypXYGho8/0.jpg' height='240' width='320' time='00:01:41.500'/></media:group><yt:statistics viewCount='138864' favoriteCount='2474'/><gd:rating min='1' max='5' numRaters='4626' average='4.95'/><gd:comments><gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/C71ypXYGho8/comments' countHint='27'/></gd:comments></entry>
 <entry><id>http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw</id><published>2008-02-15T04:31:45.000-08:00</published><updated>2008-05-14T05:09:42.000-07:00</updated><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='extreme'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='cam'/><category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' term='Sports' label='Sports'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='alcala'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='kani'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='helmet'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='campillo'/><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#video'/><category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' term='pato'/><category scheme='
 http://gdata.youtube.com/schemas/2007/keywords.cat' term='dirt'/><title type='text'>extreme helmet cam Kani, Keil and Pato</title><content type='text'>trimmed</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=gsVaTyb1tBw'/><link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw/responses'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw/related'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/recently_featured/gsVaTyb1tBw'/><author><name>peraltamagic</name><uri>http://gdata.youtube.com/feeds/api/users/peraltamagic</uri></author><media:group><media:title type='plain'>extreme helmet cam Kani, Keil and Pato</media:title><media:description type='plain'>trimmed</media:description><media:keywords
 >alcala, cam, campillo, dirt, extreme, helmet, kani, pato</media:keywords><yt:duration seconds='31'/><media:category label='Sports' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Sports</media:category><media:content url='http://www.youtube.com/v/gsVaTyb1tBw' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='31' yt:format='5'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQkctPUmT1rFghMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='31' yt:format='1'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQkctPUmT1rFghMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='31' yt:format='6'/><media:player url='http://www.youtube.com/watch?v=gsVaTyb1tBw'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1tBw/2.jpg' height='97' width='130' time='00:00:15.500'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1
 tBw/1.jpg' height='97' width='130' time='00:00:07.750'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1tBw/3.jpg' height='97' width='130' time='00:00:23.250'/><media:thumbnail url='http://img.youtube.com/vi/gsVaTyb1tBw/0.jpg' height='240' width='320' time='00:00:15.500'/></media:group><yt:statistics viewCount='489941' favoriteCount='561'/><gd:rating min='1' max='5' numRaters='1255' average='4.11'/><gd:comments><gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/gsVaTyb1tBw/comments' countHint='1116'/></gd:comments></entry>
 </feed>"""
 
@@ -2297,18 +2297,18 @@
   </entry>              
 </feed>"""
 
-YOUTUBE_PLAYLIST_VIDEO_FEED = """<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' xmlns:media='http://search.yahoo.com/mrss/' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:gd='http://schemas.google.com/g/2005'><id>http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505</id><updated>2008-05-16T12:03:17.000-07:00</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#playlist'/><category scheme='http://gdata.youtube.com/schemas/2007/tags.cat' term='videos'/><category scheme='http://gdata.youtube.com/schemas/2007/tags.cat' term='python'/><title type='text'>Test Playlist</title><subtitle type='text'>Test playlist 1</subtitle><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='alternate' type='text/html' href='http:/
 /www.youtube.com/view_play_list?p=BCB3BB96DF51B505'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505?start-index=1&amp;max-results=25'/><author><name>gdpython</name><uri>http://gdata.youtube.com/feeds/api/users/gdpython</uri></author><generator version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator><openSearch:totalResults>1</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><media:group><media:title type='plain'>Test Playlist</media:title><media:description type='plain'>Test playlist 1</media:description><media:content url='http://www.youtube.com/ep.swf?id=BCB3BB96DF51B505' type='application/x-shockwave-flash' yt:format='5'/></media:group><entry><id>http://gdata.youtube.c
 om/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F888</id><updated>2008-05-16T20:54:08.520Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#playlist'/><title type='text'>Uploading YouTube Videos with the PHP Client Library</title><content type='text'>Jochen Hartmann demonstrates the basics of how to use the PHP Client Library with the YouTube Data API.
-
-PHP Developer's Guide:
-http://code.google.com/apis/youtube/developers_guide_php.html
-
-Other documentation:
-http://code.google.com/apis/youtube/</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=iIp7OnHXBlo'/><link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/responses'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/related'/><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F888'/><author><name>GoogleDevelopers</name><uri>http://gdata.youtube.com/feeds/api/users/googledevelopers</uri></author><media:group><media:title type='plain'>Uploading YouTube Videos with the PHP Client Library</media:title><media:description type='plain'>Jochen Hartmann demonstrates the
  basics of how to use the PHP Client Library with the YouTube Data API.
-
-PHP Developer's Guide:
-http://code.google.com/apis/youtube/developers_guide_php.html
-
-Other documentation:
+YOUTUBE_PLAYLIST_VIDEO_FEED = """<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' xmlns:media='http://search.yahoo.com/mrss/' xmlns:yt='http://gdata.youtube.com/schemas/2007' xmlns:gd='http://schemas.google.com/g/2005'><id>http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505</id><updated>2008-05-16T12:03:17.000-07:00</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#playlist'/><category scheme='http://gdata.youtube.com/schemas/2007/tags.cat' term='videos'/><category scheme='http://gdata.youtube.com/schemas/2007/tags.cat' term='python'/><title type='text'>Test Playlist</title><subtitle type='text'>Test playlist 1</subtitle><logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo><link rel='alternate' type='text/html' href='http:/
 /www.youtube.com/view_play_list?p=BCB3BB96DF51B505'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505?start-index=1&amp;max-results=25'/><author><name>gdpython</name><uri>http://gdata.youtube.com/feeds/api/users/gdpython</uri></author><generator version='beta' uri='http://gdata.youtube.com/'>YouTube data API</generator><openSearch:totalResults>1</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><media:group><media:title type='plain'>Test Playlist</media:title><media:description type='plain'>Test playlist 1</media:description><media:content url='http://www.youtube.com/ep.swf?id=BCB3BB96DF51B505' type='application/x-shockwave-flash' yt:format='5'/></media:group><entry><id>http://gdata.youtube.c
 om/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F888</id><updated>2008-05-16T20:54:08.520Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#playlist'/><title type='text'>Uploading YouTube Videos with the PHP Client Library</title><content type='text'>Jochen Hartmann demonstrates the basics of how to use the PHP Client Library with the YouTube Data API.
+
+PHP Developer's Guide:
+http://code.google.com/apis/youtube/developers_guide_php.html
+
+Other documentation:
+http://code.google.com/apis/youtube/</content><link rel='alternate' type='text/html' href='http://www.youtube.com/watch?v=iIp7OnHXBlo'/><link rel='http://gdata.youtube.com/schemas/2007#video.responses' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/responses'/><link rel='http://gdata.youtube.com/schemas/2007#video.related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/related'/><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/playlists/BCB3BB96DF51B505/B0F29389E537F888'/><author><name>GoogleDevelopers</name><uri>http://gdata.youtube.com/feeds/api/users/googledevelopers</uri></author><media:group><media:title type='plain'>Uploading YouTube Videos with the PHP Client Library</media:title><media:description type='plain'>Jochen Hartmann demonstrates the
  basics of how to use the PHP Client Library with the YouTube Data API.
+
+PHP Developer's Guide:
+http://code.google.com/apis/youtube/developers_guide_php.html
+
+Other documentation:
 http://code.google.com/apis/youtube/</media:description><media:keywords>api, data, demo, php, screencast, tutorial, uploading, walkthrough, youtube</media:keywords><yt:duration seconds='466'/><media:category label='Education' scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Education</media:category><media:content url='http://www.youtube.com/v/iIp7OnHXBlo' type='application/x-shockwave-flash' medium='video' isDefault='true' expression='full' duration='466' yt:format='5'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQlaBtdxOnuKiBMYDSANFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='466' yt:format='1'/><media:content url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQlaBtdxOnuKiBMYESARFEgGDA==/0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='466' yt:format='6'/><media:player url='http://www.youtube.com/watch?v=iIp7OnHXBlo'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/2.jpg' h
 eight='97' width='130' time='00:03:53'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/1.jpg' height='97' width='130' time='00:01:56.500'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/3.jpg' height='97' width='130' time='00:05:49.500'/><media:thumbnail url='http://img.youtube.com/vi/iIp7OnHXBlo/0.jpg' height='240' width='320' time='00:03:53'/></media:group><yt:statistics viewCount='1550' favoriteCount='5'/><gd:rating min='1' max='5' numRaters='3' average='4.67'/><yt:location>undefined</yt:location><gd:comments><gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/iIp7OnHXBlo/comments' countHint='2'/></gd:comments><yt:position>1</yt:position></entry></feed>"""
 
 YOUTUBE_SUBSCRIPTION_FEED = """<?xml version='1.0' encoding='UTF-8'?>
@@ -2470,32 +2470,47 @@
       <id>http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher</id><published>2008-02-26T14:13:03.000-08:00</published><updated>2008-05-16T19:24:34.916Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://gdata.youtube.com/schemas/2007#friend'/><title type='text'>testjfisher</title><link rel='related' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/testjfisher'/><link rel='alternate' type='text/html' href='http://www.youtube.com/profile?user=testjfisher'/><link rel='self' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher'/><link rel='edit' type='application/atom+xml' href='http://gdata.youtube.com/feeds/users/apitestjhartmann/contacts/testjfisher'/><author><name>apitestjhartmann</name><uri>http://gdata.youtube.com/feeds/users/apitestjhartmann</uri></author><yt:username>testjfisher</yt:username><yt:status>pending</yt:status></entry>
 </feed>"""
 
-
 NEW_CONTACT = """<?xml version='1.0' encoding='UTF-8'?>
-<atom:entry xmlns:atom='http://www.w3.org/2005/Atom'
-    xmlns:gd='http://schemas.google.com/g/2005'>
-  <atom:category scheme='http://schemas.google.com/g/2005#kind'
+<entry xmlns='http://www.w3.org/2005/Atom' 
+       xmlns:gd='http://schemas.google.com/g/2005'
+       xmlns:gContact='http://schemas.google.com/contact/2008'>
+  <id>http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/8411573</id>
+  <updated>2008-02-28T18:47:02.303Z</updated>
+  <category scheme='http://schemas.google.com/g/2005#kind'
     term='http://schemas.google.com/contact/2008#contact' />
-  <atom:title type='text'>Elizabeth Bennet</atom:title>
-  <atom:content type='text'>Notes</atom:content>
+  <title type='text'>Fitzgerald</title>
+  <content type='text'>Notes</content>
+  <link rel='self' type='application/atom+xml'
+    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full/8411573' />
+  <link rel='edit' type='application/atom+xml'
+    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full/8411573/1204224422303000' />
   <gd:email rel='http://schemas.google.com/g/2005#work'
     address='liz gmail com' />
   <gd:email rel='http://schemas.google.com/g/2005#home'
     address='liz example org' />
   <gd:phoneNumber rel='http://schemas.google.com/g/2005#work'
     primary='true'>(206)555-1212</gd:phoneNumber>
+  <gd:phoneNumber rel='http://schemas.google.com/g/2005#other' 
+    primary='true'>456-123-2133</gd:phoneNumber>
   <gd:phoneNumber rel='http://schemas.google.com/g/2005#home'>(206)555-1213</gd:phoneNumber>
+  <gd:extendedProperty name="pet" value="hamster" />
+  <gd:extendedProperty name="cousine"> 
+    <italian />
+  </gd:extendedProperty>
+  <gContact:groupMembershipInfo deleted="false" href="http://google.com/m8/feeds/groups/liz%40gmail.com/base/270f"; /> 
   <gd:im address='liz gmail com'
     protocol='http://schemas.google.com/g/2005#GOOGLE_TALK'
     rel='http://schemas.google.com/g/2005#home' />
   <gd:postalAddress rel='http://schemas.google.com/g/2005#work'
     primary='true'>1600 Amphitheatre Pkwy Mountain View</gd:postalAddress>
-</atom:entry>"""
+</entry>"""
 
 CONTACTS_FEED = """<?xml version='1.0' encoding='UTF-8'?>
 <feed xmlns='http://www.w3.org/2005/Atom'
     xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
-    xmlns:gd='http://schemas.google.com/g/2005'>
+    xmlns:gd='http://schemas.google.com/g/2005'
+    xmlns:gContact='http://schemas.google.com/contact/2008'
+    xmlns:batch='http://schemas.google.com/gdata/batch'>
   <id>http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base</id>
   <updated>2008-03-05T12:36:38.836Z</updated>
   <category scheme='http://schemas.google.com/g/2005#kind'
@@ -2503,35 +2518,114 @@
   <title type='text'>Contacts</title>
   <link rel='http://schemas.google.com/g/2005#feed'
     type='application/atom+xml'
-    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base' />
+    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full' />
   <link rel='http://schemas.google.com/g/2005#post'
     type='application/atom+xml'
-    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base' />
+    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full' />
+  <link rel='http://schemas.google.com/g/2005#batch'
+    type='application/atom+xml'
+    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full/batch' />
   <link rel='self' type='application/atom+xml'
-    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base?max-results=25' />
+    href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full?max-results=25' />
   <author>
     <name>Elizabeth Bennet</name>
     <email>liz gmail com</email>
   </author>
-  <generator version='1.0' uri='http://www.google.com/m8/feeds/contacts'>Contacts</generator>
+  <generator version='1.0' uri='http://www.google.com/m8/feeds/contacts'>
+    Contacts
+  </generator>
   <openSearch:totalResults>1</openSearch:totalResults>
   <openSearch:startIndex>1</openSearch:startIndex>
   <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
   <entry>
-    <id>http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de</id>
+    <id>
+      http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de
+    </id>
     <updated>2008-03-05T12:36:38.835Z</updated>
     <category scheme='http://schemas.google.com/g/2005#kind'
       term='http://schemas.google.com/contact/2008#contact' />
     <title type='text'>Fitzgerald</title>
+    <link rel="http://schemas.google.com/contacts/2008/rel#photo"; type="image/*" 
+      href="http://google.com/m8/feeds/photos/media/liz%40gmail.com/c9012de"/>
+    <link rel="http://schemas.google.com/contacts/2008/rel#edit-photo"; type="image/*" 
+      href="http://www.google.com/m8/feeds/photos/media/liz%40gmail.com/c9012de/photo4524"/>
     <link rel='self' type='application/atom+xml'
-      href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de' />
+      href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full/c9012de' />
     <link rel='edit' type='application/atom+xml'
-      href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/base/c9012de/1204720598835000' />
+      href='http://www.google.com/m8/feeds/contacts/liz%40gmail.com/full/c9012de/1204720598835000' />
     <gd:phoneNumber rel='http://schemas.google.com/g/2005#home'
-      primary='true'>456</gd:phoneNumber>
+      primary='true'>
+      456
+    </gd:phoneNumber>
+    <gd:extendedProperty name="pet" value="hamster" />
+    <gContact:groupMembershipInfo deleted="false" href="http://google.com/m8/feeds/groups/liz%40gmail.com/base/270f"; />
   </entry>
 </feed>"""
 
+
+CONTACT_GROUPS_FEED = """<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"; 
+  xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/"; 
+  xmlns:gContact="http://schemas.google.com/contact/2008"; 
+  xmlns:batch="http://schemas.google.com/gdata/batch"; 
+  xmlns:gd="http://schemas.google.com/g/2005";>
+  <id>jo gmail com</id>
+  <updated>2008-05-21T21:11:25.237Z</updated>
+  <category scheme="http://schemas.google.com/g/2005#kind"; term="http://schemas.google.com/contact/2008#group"/>
+  <title type="text">Jo's Contact Groups</title>
+  <link rel="alternate" type="text/html" href="http://www.google.com/"/>
+  <link rel="http://schemas.google.com/g/2005#feed"; 
+    type="application/atom+xml" 
+    href="http://google.m/m8/feeds/groups/jo%40gmail.com/thin"/>
+  <link rel="http://schemas.google.com/g/2005#post"; 
+      type="application/atom+xml" 
+      href="http://google.m/m8/feeds/groups/jo%40gmail.com/thin"/>
+  <link rel="http://schemas.google.com/g/2005#batch"; 
+      type="application/atom+xml" 
+      href="http://googleom/m8/feeds/groups/jo%40gmail.com/thin/batch"/>
+  <link rel="self" 
+      type="application/atom+xml" 
+      href="http://google.com/m8/feeds/groups/jo%40gmail.com/thin?max-results=25"/>
+  <author>
+    <name>Jo Brown</name>
+    <email>jo gmail com</email>
+  </author>
+  <generator version="1.0" uri="http://google.com/m8/feeds";>Contacts</generator>
+  <openSearch:totalResults>3</openSearch:totalResults>
+  <openSearch:startIndex>1</openSearch:startIndex>
+  <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
+  <entry>
+    <id>http://google.com/m8/feeds/groups/jo%40gmail.com/base/270f</id>
+    <updated>2008-05-14T13:10:19.070Z</updated>
+    <category scheme="http://schemas.google.com/g/2005#kind"; term="http://schemas.google.com/contact/2008#group"/>
+    <title type="text">joggers</title>
+    <content type="text">joggers</content>
+    <link rel="self" type="application/atom+xml" 
+        href="http://google.com/m8/feeds/groups/jo%40gmail.com/thin/270f"/>
+    <link rel="edit" type="application/atom+xml"
+        href="http://google.com/m8/feeds/groups/jo%40gmail.com/thin/270f/1210770619070000"/>
+  </entry>
+</feed>"""
+
+CONTACT_GROUP_ENTRY = """<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns='http://www.w3.org/2005/Atom'
+       xmlns:gd="http://schemas.google.com/g/2005";>
+    <category scheme="http://schemas.google.com/g/2005#kind";
+        term="http://schemas.google.com/g/2005#group"/>
+    <id>http://www.google.com/feeds/groups/jo%40gmail.com/base/1234</id>
+    <published>2005-01-18T21:00:00Z</published>
+    <updated>2006-01-01T00:00:00Z</updated>
+    <title type="text">Salsa group</title>
+    <content type="text">Salsa group</content>
+    <link rel='self' type='application/atom+xml'
+        href= 'http://www.google.com/m8/feeds/groups/jo%40gmail.com/full/2' />
+    <link rel='edit' type='application/atom+xml'
+        href='http://www.google.com/m8/feeds/groups/jo%40gmail.com/full/2/0'/>
+    <gd:extendedProperty name="more info about the group">
+        <info>Very nice people.</info>
+    </gd:extendedProperty>
+</entry>"""
+
 BLOG_ENTRY = """<entry xmlns='http://www.w3.org/2005/Atom'>
   <id>tag:blogger.com,1999:blog-blogID.post-postID</id>
   <published>2006-08-02T18:44:43.089-07:00</published>
@@ -2643,5 +2737,102 @@
     <author>
       <name>Blog Author name</name>
     </author>
+    <thr:in-reply-to xmlns:thr='http://purl.org/syndication/thread/1.0' 
+        href='http://blogName.blogspot.com/2007/04/first-post.html' 
+        ref='tag:blogger.com,1999:blog-blogID.post-postID' 
+        source='http://blogName.blogspot.com/feeds/posts/default/postID'
+        type='text/html' />
+  </entry>
+</feed>"""
+
+
+SITES_FEED = """<feed xmlns="http://www.w3.org/2005/Atom";
+  xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/";
+  xmlns:gd="http://schemas.google.com/g/2005";
+  xmlns:wt="http://schemas.google.com/webmasters/tools/2007";>
+  <id>https://www.google.com/webmasters/tools/feeds/sites</id>
+  <title>Sites</title>
+  <openSearch:startIndex>1</openSearch:startIndex>
+  <category scheme="http://schemas.google.com/g/2005#kind"; term="http://schemas.google.com/webmasters/tools/2007#sites-feed"; />
+  <link href="http://www.google.com/webmasters/tools/feeds/sites"; rel="http://schemas.google.com/g/2005#feed"; type="application/atom+xml" />
+  <link href="http://www.google.com/webmasters/tools/feeds/sites"; rel="http://schemas.google.com/g/2005#post"; type="application/atom+xml" />
+  <link href="http://www.google.com/webmasters/tools/feeds/sites"; rel="self" type="application/atom+xml" />
+  <updated>2008-10-02T07:26:51.833Z</updated>
+  <entry>
+    <id>http://www.example.com</id>
+    <title type="text">http://www.example.com</title>
+    <link href="http://www.google.com/webmasters/tools/feeds/sites/http%3A%2F%2Fwww.example.com%2F"; rel="self" type="application/atom+xml"/>
+    <link href="http://www.google.com/webmasters/tools/feeds/sites/http%3A%2F%2Fwww.example.com%2F"; rel="edit" type="application/atom+xml"/>
+    <content src="http://www.example.com"/>
+    <updated>2007-11-17T18:27:32.543Z</updated>
+    <category scheme="http://schemas.google.com/g/2005#kind"; term="http://schemas.google.com/webmasters/tools/2007#site-info"/>
+    <gd:entryLink rel="http://schemas.google.com/webmasters/tools/2007#verification"; 
+      href="https://www.google.com/webmasters/tools/feeds/http%3A%2F%2Fwww%2Eexample%2Ecom%2F/verification"; />
+    <gd:entryLink rel="http://schemas.google.com/webmasters/tools/2007#sitemaps"; 
+      href="https://www.google.com/webmasters/tools/feeds/http%3A%2F%2Fwww%2Eexample%2Ecom%2F/sitemaps"; />
+    <wt:indexed>true</wt:indexed>
+    <wt:crawled>2008-09-14T08:59:28.000</wt:crawled>
+    <wt:geolocation>US</wt:geolocation>
+    <wt:preferred-domain>none</wt:preferred-domain>
+    <wt:crawl-rate>normal</wt:crawl-rate>
+    <wt:enhanced-image-search>true</wt:enhanced-image-search>
+    <wt:verified>false</wt:verified>
+    <wt:verification-method type="metatag" in-use="false"><meta name="verify-v1" content="a2Ai"/>
+      </wt:verification-method>
+    <wt:verification-method type="htmlpage" in-use="false">456456-google.html</wt:verification-method>
+  </entry>
+</feed>"""
+
+
+SITEMAPS_FEED = """<feed xmlns="http://www.w3.org/2005/Atom"; 
+  xmlns:wt="http://schemas.google.com/webmasters/tools/2007";>
+  <id>http://www.example.com</id>
+  <title type="text">http://www.example.com/</title>
+  <updated>2006-11-17T18:27:32.543Z</updated>
+  <link rel="self" type="application/atom+xml" 
+    href="https://www.google.com/webmasters/tools/feeds/http%3A%2F%2Fwww%2Eexample%2Ecom%2F/sitemaps"; />
+  <category scheme='http://schemas.google.com/g/2005#kind' 
+    term='http://schemas.google.com/webmasters/tools/2007#sitemaps-feed'/>
+  <wt:sitemap-mobile>
+    <wt:markup-language>HTML</wt:markup-language>
+    <wt:markup-language>WAP</wt:markup-language>
+  </wt:sitemap-mobile>
+  <wt:sitemap-news>
+    <wt:publication-label>Value1</wt:publication-label>
+    <wt:publication-label>Value2</wt:publication-label>
+    <wt:publication-label>Value3</wt:publication-label>
+  </wt:sitemap-news>
+  <entry>
+    <id>http://www.example.com/sitemap-index.xml</id>
+    <title type="text">http://www.example.com/sitemap-index.xml</title>
+    <category scheme='http://schemas.google.com/g/2005#kind' 
+      term='http://schemas.google.com/webmasters/tools/2007#sitemap-regular'/>
+    <updated>2006-11-17T18:27:32.543Z</updated>
+    <wt:sitemap-type>WEB</wt:sitemap-type>
+    <wt:sitemap-status>StatusValue</wt:sitemap-status>
+    <wt:sitemap-last-downloaded>2006-11-18T19:27:32.543Z</wt:sitemap-last-downloaded>
+    <wt:sitemap-url-count>102</wt:sitemap-url-count>
+  </entry>
+  <entry>
+    <id>http://www.example.com/mobile/sitemap-index.xml</id>
+    <title type="text">http://www.example.com/mobile/sitemap-index.xml</title>
+    <category scheme='http://schemas.google.com/g/2005#kind' 
+      term='http://schemas.google.com/webmasters/tools/2007#sitemap-mobile'/>
+    <updated>2006-11-17T18:27:32.543Z</updated>
+    <wt:sitemap-status>StatusValue</wt:sitemap-status>
+    <wt:sitemap-last-downloaded>2006-11-18T19:27:32.543Z</wt:sitemap-last-downloaded>
+    <wt:sitemap-url-count>102</wt:sitemap-url-count>
+    <wt:sitemap-mobile-markup-language>HTML</wt:sitemap-mobile-markup-language>
+  </entry>
+  <entry>
+    <id>http://www.example.com/news/sitemap-index.xml</id>
+    <title type="text">http://www.example.com/news/sitemap-index.xml</title>
+    <category scheme='http://schemas.google.com/g/2005#kind' 
+      term='http://schemas.google.com/webmasters/tools/2007#sitemap-news'/>
+    <updated>2006-11-17T18:27:32.543Z</updated>
+    <wt:sitemap-status>StatusValue</wt:sitemap-status>
+    <wt:sitemap-last-downloaded>2006-11-18T19:27:32.543Z</wt:sitemap-last-downloaded>
+    <wt:sitemap-url-count>102</wt:sitemap-url-count>
+    <wt:sitemap-news-publication-label>LabelValue</wt:sitemap-news-publication-label>
   </entry>
 </feed>"""

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/BaseDB.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/BaseDB.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,120 @@
+"""Base class for SharedKeyDB and VerifierDB."""
+
+import anydbm
+import thread
+
+class BaseDB:
+    def __init__(self, filename, type):
+        self.type = type
+        self.filename = filename
+        if self.filename:
+            self.db = None
+        else:
+            self.db = {}
+        self.lock = thread.allocate_lock()
+
+    def create(self):
+        """Create a new on-disk database.
+
+        @raise anydbm.error: If there's a problem creating the database.
+        """
+        if self.filename:
+            self.db = anydbm.open(self.filename, "n") #raises anydbm.error
+            self.db["--Reserved--type"] = self.type
+            self.db.sync()
+        else:
+            self.db = {}
+
+    def open(self):
+        """Open a pre-existing on-disk database.
+
+        @raise anydbm.error: If there's a problem opening the database.
+        @raise ValueError: If the database is not of the right type.
+        """
+        if not self.filename:
+            raise ValueError("Can only open on-disk databases")
+        self.db = anydbm.open(self.filename, "w") #raises anydbm.error
+        try:
+            if self.db["--Reserved--type"] != self.type:
+                raise ValueError("Not a %s database" % self.type)
+        except KeyError:
+            raise ValueError("Not a recognized database")
+
+    def __getitem__(self, username):
+        if self.db == None:
+            raise AssertionError("DB not open")
+
+        self.lock.acquire()
+        try:
+            valueStr = self.db[username]
+        finally:
+            self.lock.release()
+
+        return self._getItem(username, valueStr)
+
+    def __setitem__(self, username, value):
+        if self.db == None:
+            raise AssertionError("DB not open")
+
+        valueStr = self._setItem(username, value)
+
+        self.lock.acquire()
+        try:
+            self.db[username] = valueStr
+            if self.filename:
+                self.db.sync()
+        finally:
+            self.lock.release()
+
+    def __delitem__(self, username):
+        if self.db == None:
+            raise AssertionError("DB not open")
+
+        self.lock.acquire()
+        try:
+            del(self.db[username])
+            if self.filename:
+                self.db.sync()
+        finally:
+            self.lock.release()
+
+    def __contains__(self, username):
+        """Check if the database contains the specified username.
+
+        @type username: str
+        @param username: The username to check for.
+
+        @rtype: bool
+        @return: True if the database contains the username, False
+        otherwise.
+
+        """
+        if self.db == None:
+            raise AssertionError("DB not open")
+
+        self.lock.acquire()
+        try:
+            return self.db.has_key(username)
+        finally:
+            self.lock.release()
+
+    def check(self, username, param):
+        value = self.__getitem__(username)
+        return self._checkItem(value, username, param)
+
+    def keys(self):
+        """Return a list of usernames in the database.
+
+        @rtype: list
+        @return: The usernames in the database.
+        """
+        if self.db == None:
+            raise AssertionError("DB not open")
+
+        self.lock.acquire()
+        try:
+            usernames = self.db.keys()
+        finally:
+            self.lock.release()
+        usernames = [u for u in usernames if not u.startswith("--Reserved--")]
+        return usernames
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/Checker.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/Checker.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,146 @@
+"""Class for post-handshake certificate checking."""
+
+from utils.cryptomath import hashAndBase64
+from X509 import X509
+from X509CertChain import X509CertChain
+from errors import *
+
+
+class Checker:
+    """This class is passed to a handshake function to check the other
+    party's certificate chain.
+
+    If a handshake function completes successfully, but the Checker
+    judges the other party's certificate chain to be missing or
+    inadequate, a subclass of
+    L{tlslite.errors.TLSAuthenticationError} will be raised.
+
+    Currently, the Checker can check either an X.509 or a cryptoID
+    chain (for the latter, cryptoIDlib must be installed).
+    """
+
+    def __init__(self, cryptoID=None, protocol=None,
+                 x509Fingerprint=None,
+                 x509TrustList=None, x509CommonName=None,
+                 checkResumedSession=False):
+        """Create a new Checker instance.
+
+        You must pass in one of these argument combinations:
+         - cryptoID[, protocol] (requires cryptoIDlib)
+         - x509Fingerprint
+         - x509TrustList[, x509CommonName] (requires cryptlib_py)
+
+        @type cryptoID: str
+        @param cryptoID: A cryptoID which the other party's certificate
+        chain must match.  The cryptoIDlib module must be installed.
+        Mutually exclusive with all of the 'x509...' arguments.
+
+        @type protocol: str
+        @param protocol: A cryptoID protocol URI which the other
+        party's certificate chain must match.  Requires the 'cryptoID'
+        argument.
+
+        @type x509Fingerprint: str
+        @param x509Fingerprint: A hex-encoded X.509 end-entity
+        fingerprint which the other party's end-entity certificate must
+        match.  Mutually exclusive with the 'cryptoID' and
+        'x509TrustList' arguments.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        other party must present a certificate chain which extends to
+        one of these root certificates.  The cryptlib_py module must be
+        installed.  Mutually exclusive with the 'cryptoID' and
+        'x509Fingerprint' arguments.
+
+        @type x509CommonName: str
+        @param x509CommonName: The end-entity certificate's 'CN' field
+        must match this value.  For a web server, this is typically a
+        server name such as 'www.amazon.com'.  Mutually exclusive with
+        the 'cryptoID' and 'x509Fingerprint' arguments.  Requires the
+        'x509TrustList' argument.
+
+        @type checkResumedSession: bool
+        @param checkResumedSession: If resumed sessions should be
+        checked.  This defaults to False, on the theory that if the
+        session was checked once, we don't need to bother
+        re-checking it.
+        """
+
+        if cryptoID and (x509Fingerprint or x509TrustList):
+            raise ValueError()
+        if x509Fingerprint and x509TrustList:
+            raise ValueError()
+        if x509CommonName and not x509TrustList:
+            raise ValueError()
+        if protocol and not cryptoID:
+            raise ValueError()
+        if cryptoID:
+            import cryptoIDlib #So we raise an error here
+        if x509TrustList:
+            import cryptlib_py #So we raise an error here
+        self.cryptoID = cryptoID
+        self.protocol = protocol
+        self.x509Fingerprint = x509Fingerprint
+        self.x509TrustList = x509TrustList
+        self.x509CommonName = x509CommonName
+        self.checkResumedSession = checkResumedSession
+
+    def __call__(self, connection):
+        """Check a TLSConnection.
+
+        When a Checker is passed to a handshake function, this will
+        be called at the end of the function.
+
+        @type connection: L{tlslite.TLSConnection.TLSConnection}
+        @param connection: The TLSConnection to examine.
+
+        @raise tlslite.errors.TLSAuthenticationError: If the other
+        party's certificate chain is missing or bad.
+        """
+        if not self.checkResumedSession and connection.resumed:
+            return
+
+        if self.cryptoID or self.x509Fingerprint or self.x509TrustList:
+            if connection._client:
+                chain = connection.session.serverCertChain
+            else:
+                chain = connection.session.clientCertChain
+
+            if self.x509Fingerprint or self.x509TrustList:
+                if isinstance(chain, X509CertChain):
+                    if self.x509Fingerprint:
+                        if chain.getFingerprint() != self.x509Fingerprint:
+                            raise TLSFingerprintError(\
+                                "X.509 fingerprint mismatch: %s, %s" % \
+                                (chain.getFingerprint(), self.x509Fingerprint))
+                    else: #self.x509TrustList
+                        if not chain.validate(self.x509TrustList):
+                            raise TLSValidationError("X.509 validation failure")
+                        if self.x509CommonName and \
+                               (chain.getCommonName() != self.x509CommonName):
+                           raise TLSAuthorizationError(\
+                               "X.509 Common Name mismatch: %s, %s" % \
+                               (chain.getCommonName(), self.x509CommonName))
+                elif chain:
+                    raise TLSAuthenticationTypeError()
+                else:
+                    raise TLSNoAuthenticationError()
+            elif self.cryptoID:
+                import cryptoIDlib.CertChain
+                if isinstance(chain, cryptoIDlib.CertChain.CertChain):
+                    if chain.cryptoID != self.cryptoID:
+                        raise TLSFingerprintError(\
+                            "cryptoID mismatch: %s, %s" % \
+                            (chain.cryptoID, self.cryptoID))
+                    if self.protocol:
+                        if not chain.checkProtocol(self.protocol):
+                            raise TLSAuthorizationError(\
+                            "cryptoID protocol mismatch")
+                    if not chain.validate():
+                        raise TLSValidationError("cryptoID validation failure")
+                elif chain:
+                    raise TLSAuthenticationTypeError()
+                else:
+                    raise TLSNoAuthenticationError()
+

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/FileObject.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/FileObject.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,220 @@
+"""Class returned by TLSConnection.makefile()."""
+
+class FileObject:
+    """This class provides a file object interface to a
+    L{tlslite.TLSConnection.TLSConnection}.
+
+    Call makefile() on a TLSConnection to create a FileObject instance.
+
+    This class was copied, with minor modifications, from the
+    _fileobject class in socket.py.  Note that fileno() is not
+    implemented."""
+
+    default_bufsize = 16384 #TREV: changed from 8192
+
+    def __init__(self, sock, mode='rb', bufsize=-1):
+        self._sock = sock
+        self.mode = mode # Not actually used in this version
+        if bufsize < 0:
+            bufsize = self.default_bufsize
+        self.bufsize = bufsize
+        self.softspace = False
+        if bufsize == 0:
+            self._rbufsize = 1
+        elif bufsize == 1:
+            self._rbufsize = self.default_bufsize
+        else:
+            self._rbufsize = bufsize
+        self._wbufsize = bufsize
+        self._rbuf = "" # A string
+        self._wbuf = [] # A list of strings
+
+    def _getclosed(self):
+        return self._sock is not None
+    closed = property(_getclosed, doc="True if the file is closed")
+
+    def close(self):
+        try:
+            if self._sock:
+                for result in self._sock._decrefAsync(): #TREV
+                    pass
+        finally:
+            self._sock = None
+
+    def __del__(self):
+        try:
+            self.close()
+        except:
+            # close() may fail if __init__ didn't complete
+            pass
+
+    def flush(self):
+        if self._wbuf:
+            buffer = "".join(self._wbuf)
+            self._wbuf = []
+            self._sock.sendall(buffer)
+
+    #def fileno(self):
+    #    raise NotImplementedError() #TREV
+
+    def write(self, data):
+        data = str(data) # XXX Should really reject non-string non-buffers
+        if not data:
+            return
+        self._wbuf.append(data)
+        if (self._wbufsize == 0 or
+            self._wbufsize == 1 and '\n' in data or
+            self._get_wbuf_len() >= self._wbufsize):
+            self.flush()
+
+    def writelines(self, list):
+        # XXX We could do better here for very long lists
+        # XXX Should really reject non-string non-buffers
+        self._wbuf.extend(filter(None, map(str, list)))
+        if (self._wbufsize <= 1 or
+            self._get_wbuf_len() >= self._wbufsize):
+            self.flush()
+
+    def _get_wbuf_len(self):
+        buf_len = 0
+        for x in self._wbuf:
+            buf_len += len(x)
+        return buf_len
+
+    def read(self, size=-1):
+        data = self._rbuf
+        if size < 0:
+            # Read until EOF
+            buffers = []
+            if data:
+                buffers.append(data)
+            self._rbuf = ""
+            if self._rbufsize <= 1:
+                recv_size = self.default_bufsize
+            else:
+                recv_size = self._rbufsize
+            while True:
+                data = self._sock.recv(recv_size)
+                if not data:
+                    break
+                buffers.append(data)
+            return "".join(buffers)
+        else:
+            # Read until size bytes or EOF seen, whichever comes first
+            buf_len = len(data)
+            if buf_len >= size:
+                self._rbuf = data[size:]
+                return data[:size]
+            buffers = []
+            if data:
+                buffers.append(data)
+            self._rbuf = ""
+            while True:
+                left = size - buf_len
+                recv_size = max(self._rbufsize, left)
+                data = self._sock.recv(recv_size)
+                if not data:
+                    break
+                buffers.append(data)
+                n = len(data)
+                if n >= left:
+                    self._rbuf = data[left:]
+                    buffers[-1] = data[:left]
+                    break
+                buf_len += n
+            return "".join(buffers)
+
+    def readline(self, size=-1):
+        data = self._rbuf
+        if size < 0:
+            # Read until \n or EOF, whichever comes first
+            if self._rbufsize <= 1:
+                # Speed up unbuffered case
+                assert data == ""
+                buffers = []
+                recv = self._sock.recv
+                while data != "\n":
+                    data = recv(1)
+                    if not data:
+                        break
+                    buffers.append(data)
+                return "".join(buffers)
+            nl = data.find('\n')
+            if nl >= 0:
+                nl += 1
+                self._rbuf = data[nl:]
+                return data[:nl]
+            buffers = []
+            if data:
+                buffers.append(data)
+            self._rbuf = ""
+            while True:
+                data = self._sock.recv(self._rbufsize)
+                if not data:
+                    break
+                buffers.append(data)
+                nl = data.find('\n')
+                if nl >= 0:
+                    nl += 1
+                    self._rbuf = data[nl:]
+                    buffers[-1] = data[:nl]
+                    break
+            return "".join(buffers)
+        else:
+            # Read until size bytes or \n or EOF seen, whichever comes first
+            nl = data.find('\n', 0, size)
+            if nl >= 0:
+                nl += 1
+                self._rbuf = data[nl:]
+                return data[:nl]
+            buf_len = len(data)
+            if buf_len >= size:
+                self._rbuf = data[size:]
+                return data[:size]
+            buffers = []
+            if data:
+                buffers.append(data)
+            self._rbuf = ""
+            while True:
+                data = self._sock.recv(self._rbufsize)
+                if not data:
+                    break
+                buffers.append(data)
+                left = size - buf_len
+                nl = data.find('\n', 0, left)
+                if nl >= 0:
+                    nl += 1
+                    self._rbuf = data[nl:]
+                    buffers[-1] = data[:nl]
+                    break
+                n = len(data)
+                if n >= left:
+                    self._rbuf = data[left:]
+                    buffers[-1] = data[:left]
+                    break
+                buf_len += n
+            return "".join(buffers)
+
+    def readlines(self, sizehint=0):
+        total = 0
+        list = []
+        while True:
+            line = self.readline()
+            if not line:
+                break
+            list.append(line)
+            total += len(line)
+            if sizehint and total >= sizehint:
+                break
+        return list
+
+    # Iterator protocols
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        line = self.readline()
+        if not line:
+            raise StopIteration
+        return line

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/HandshakeSettings.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/HandshakeSettings.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,159 @@
+"""Class for setting handshake parameters."""
+
+from constants import CertificateType
+from utils import cryptomath
+from utils import cipherfactory
+
+class HandshakeSettings:
+    """This class encapsulates various parameters that can be used with
+    a TLS handshake.
+    @sort: minKeySize, maxKeySize, cipherNames, certificateTypes,
+    minVersion, maxVersion
+
+    @type minKeySize: int
+    @ivar minKeySize: The minimum bit length for asymmetric keys.
+
+    If the other party tries to use SRP, RSA, or Diffie-Hellman
+    parameters smaller than this length, an alert will be
+    signalled.  The default is 1023.
+
+    @type maxKeySize: int
+    @ivar maxKeySize: The maximum bit length for asymmetric keys.
+
+    If the other party tries to use SRP, RSA, or Diffie-Hellman
+    parameters larger than this length, an alert will be signalled.
+    The default is 8193.
+
+    @type cipherNames: list
+    @ivar cipherNames: The allowed ciphers, in order of preference.
+
+    The allowed values in this list are 'aes256', 'aes128', '3des', and
+    'rc4'.  If these settings are used with a client handshake, they
+    determine the order of the ciphersuites offered in the ClientHello
+    message.
+
+    If these settings are used with a server handshake, the server will
+    choose whichever ciphersuite matches the earliest entry in this
+    list.
+
+    NOTE:  If '3des' is used in this list, but TLS Lite can't find an
+    add-on library that supports 3DES, then '3des' will be silently
+    removed.
+
+    The default value is ['aes256', 'aes128', '3des', 'rc4'].
+
+    @type certificateTypes: list
+    @ivar certificateTypes: The allowed certificate types, in order of
+    preference.
+
+    The allowed values in this list are 'x509' and 'cryptoID'.  This
+    list is only used with a client handshake.  The client will
+    advertise to the server which certificate types are supported, and
+    will check that the server uses one of the appropriate types.
+
+    NOTE:  If 'cryptoID' is used in this list, but cryptoIDlib is not
+    installed, then 'cryptoID' will be silently removed.
+
+    @type minVersion: tuple
+    @ivar minVersion: The minimum allowed SSL/TLS version.
+
+    This variable can be set to (3,0) for SSL 3.0, (3,1) for
+    TLS 1.0, or (3,2) for TLS 1.1.  If the other party wishes to
+    use a lower version, a protocol_version alert will be signalled.
+    The default is (3,0).
+
+    @type maxVersion: tuple
+    @ivar maxVersion: The maximum allowed SSL/TLS version.
+
+    This variable can be set to (3,0) for SSL 3.0, (3,1) for
+    TLS 1.0, or (3,2) for TLS 1.1.  If the other party wishes to
+    use a higher version, a protocol_version alert will be signalled.
+    The default is (3,2).  (WARNING: Some servers may (improperly)
+    reject clients which offer support for TLS 1.1.  In this case,
+    try lowering maxVersion to (3,1)).
+    """
+    def __init__(self):
+        self.minKeySize = 1023
+        self.maxKeySize = 8193
+        self.cipherNames = ["aes256", "aes128", "3des", "rc4"]
+        self.cipherImplementations = ["cryptlib", "openssl", "pycrypto",
+                                      "python"]
+        self.certificateTypes = ["x509", "cryptoID"]
+        self.minVersion = (3,0)
+        self.maxVersion = (3,2)
+
+    #Filters out options that are not supported
+    def _filter(self):
+        other = HandshakeSettings()
+        other.minKeySize = self.minKeySize
+        other.maxKeySize = self.maxKeySize
+        other.cipherNames = self.cipherNames
+        other.cipherImplementations = self.cipherImplementations
+        other.certificateTypes = self.certificateTypes
+        other.minVersion = self.minVersion
+        other.maxVersion = self.maxVersion
+
+        if not cipherfactory.tripleDESPresent:
+            other.cipherNames = [e for e in self.cipherNames if e != "3des"]
+        if len(other.cipherNames)==0:
+            raise ValueError("No supported ciphers")
+
+        try:
+            import cryptoIDlib
+        except ImportError:
+            other.certificateTypes = [e for e in self.certificateTypes \
+                                      if e != "cryptoID"]
+        if len(other.certificateTypes)==0:
+            raise ValueError("No supported certificate types")
+
+        if not cryptomath.cryptlibpyLoaded:
+            other.cipherImplementations = [e for e in \
+                self.cipherImplementations if e != "cryptlib"]
+        if not cryptomath.m2cryptoLoaded:
+            other.cipherImplementations = [e for e in \
+                other.cipherImplementations if e != "openssl"]
+        if not cryptomath.pycryptoLoaded:
+            other.cipherImplementations = [e for e in \
+                other.cipherImplementations if e != "pycrypto"]
+        if len(other.cipherImplementations)==0:
+            raise ValueError("No supported cipher implementations")
+
+        if other.minKeySize<512:
+            raise ValueError("minKeySize too small")
+        if other.minKeySize>16384:
+            raise ValueError("minKeySize too large")
+        if other.maxKeySize<512:
+            raise ValueError("maxKeySize too small")
+        if other.maxKeySize>16384:
+            raise ValueError("maxKeySize too large")
+        for s in other.cipherNames:
+            if s not in ("aes256", "aes128", "rc4", "3des"):
+                raise ValueError("Unknown cipher name: '%s'" % s)
+        for s in other.cipherImplementations:
+            if s not in ("cryptlib", "openssl", "python", "pycrypto"):
+                raise ValueError("Unknown cipher implementation: '%s'" % s)
+        for s in other.certificateTypes:
+            if s not in ("x509", "cryptoID"):
+                raise ValueError("Unknown certificate type: '%s'" % s)
+
+        if other.minVersion > other.maxVersion:
+            raise ValueError("Versions set incorrectly")
+
+        if not other.minVersion in ((3,0), (3,1), (3,2)):
+            raise ValueError("minVersion set incorrectly")
+
+        if not other.maxVersion in ((3,0), (3,1), (3,2)):
+            raise ValueError("maxVersion set incorrectly")
+
+        return other
+
+    def _getCertificateTypes(self):
+        l = []
+        for ct in self.certificateTypes:
+            if ct == "x509":
+                l.append(CertificateType.x509)
+            elif ct == "cryptoID":
+                l.append(CertificateType.cryptoID)
+            else:
+                raise AssertionError()
+        return l

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/Session.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/Session.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,131 @@
+"""Class representing a TLS session."""
+
+from utils.compat import *
+from mathtls import *
+from constants import *
+
+class Session:
+    """
+    This class represents a TLS session.
+
+    TLS distinguishes between connections and sessions.  A new
+    handshake creates both a connection and a session.  Data is
+    transmitted over the connection.
+
+    The session contains a more permanent record of the handshake.  The
+    session can be inspected to determine handshake results.  The
+    session can also be used to create a new connection through
+    "session resumption". If the client and server both support this,
+    they can create a new connection based on an old session without
+    the overhead of a full handshake.
+
+    The session for a L{tlslite.TLSConnection.TLSConnection} can be
+    retrieved from the connection's 'session' attribute.
+
+    @type srpUsername: str
+    @ivar srpUsername: The client's SRP username (or None).
+
+    @type sharedKeyUsername: str
+    @ivar sharedKeyUsername: The client's shared-key username (or
+    None).
+
+    @type clientCertChain: L{tlslite.X509CertChain.X509CertChain} or
+    L{cryptoIDlib.CertChain.CertChain}
+    @ivar clientCertChain: The client's certificate chain (or None).
+
+    @type serverCertChain: L{tlslite.X509CertChain.X509CertChain} or
+    L{cryptoIDlib.CertChain.CertChain}
+    @ivar serverCertChain: The server's certificate chain (or None).
+    """
+
+    def __init__(self):
+        self.masterSecret = createByteArraySequence([])
+        self.sessionID = createByteArraySequence([])
+        self.cipherSuite = 0
+        self.srpUsername = None
+        self.sharedKeyUsername = None
+        self.clientCertChain = None
+        self.serverCertChain = None
+        self.resumable = False
+        self.sharedKey = False
+
+    def _clone(self):
+        other = Session()
+        other.masterSecret = self.masterSecret
+        other.sessionID = self.sessionID
+        other.cipherSuite = self.cipherSuite
+        other.srpUsername = self.srpUsername
+        other.sharedKeyUsername = self.sharedKeyUsername
+        other.clientCertChain = self.clientCertChain
+        other.serverCertChain = self.serverCertChain
+        other.resumable = self.resumable
+        other.sharedKey = self.sharedKey
+        return other
+
+    def _calcMasterSecret(self, version, premasterSecret, clientRandom,
+                         serverRandom):
+        if version == (3,0):
+            self.masterSecret = PRF_SSL(premasterSecret,
+                                concatArrays(clientRandom, serverRandom), 48)
+        elif version in ((3,1), (3,2)):
+            self.masterSecret = PRF(premasterSecret, "master secret",
+                                concatArrays(clientRandom, serverRandom), 48)
+        else:
+            raise AssertionError()
+
+    def valid(self):
+        """If this session can be used for session resumption.
+
+        @rtype: bool
+        @return: If this session can be used for session resumption.
+        """
+        return self.resumable or self.sharedKey
+
+    def _setResumable(self, boolean):
+        #Only let it be set if this isn't a shared key
+        if not self.sharedKey:
+            #Only let it be set to True if the sessionID is non-null
+            if (not boolean) or (boolean and self.sessionID):
+                self.resumable = boolean
+
+    def getCipherName(self):
+        """Get the name of the cipher used with this connection.
+
+        @rtype: str
+        @return: The name of the cipher used with this connection.
+        Either 'aes128', 'aes256', 'rc4', or '3des'.
+        """
+        if self.cipherSuite in CipherSuite.aes128Suites:
+            return "aes128"
+        elif self.cipherSuite in CipherSuite.aes256Suites:
+            return "aes256"
+        elif self.cipherSuite in CipherSuite.rc4Suites:
+            return "rc4"
+        elif self.cipherSuite in CipherSuite.tripleDESSuites:
+            return "3des"
+        else:
+            return None
+
+    def _createSharedKey(self, sharedKeyUsername, sharedKey):
+        if len(sharedKeyUsername)>16:
+            raise ValueError()
+        if len(sharedKey)>47:
+            raise ValueError()
+
+        self.sharedKeyUsername = sharedKeyUsername
+
+        self.sessionID = createByteArrayZeros(16)
+        for x in range(len(sharedKeyUsername)):
+            self.sessionID[x] = ord(sharedKeyUsername[x])
+
+        premasterSecret = createByteArrayZeros(48)
+        sharedKey = chr(len(sharedKey)) + sharedKey
+        for x in range(48):
+            premasterSecret[x] = ord(sharedKey[x % len(sharedKey)])
+
+        self.masterSecret = PRF(premasterSecret, "shared secret",
+                                createByteArraySequence([]), 48)
+        self.sharedKey = True
+        return self
+
+

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/SessionCache.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/SessionCache.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,103 @@
+"""Class for caching TLS sessions."""
+
+import thread
+import time
+
+class SessionCache:
+    """This class is used by the server to cache TLS sessions.
+
+    Caching sessions allows the client to use TLS session resumption
+    and avoid the expense of a full handshake.  To use this class,
+    simply pass a SessionCache instance into the server handshake
+    function.
+
+    This class is thread-safe.
+    """
+
+    #References to these instances
+    #are also held by the caller, who may change the 'resumable'
+    #flag, so the SessionCache must return the same instances
+    #it was passed in.
+
+    def __init__(self, maxEntries=10000, maxAge=14400):
+        """Create a new SessionCache.
+
+        @type maxEntries: int
+        @param maxEntries: The maximum size of the cache.  When this
+        limit is reached, the oldest sessions will be deleted as
+        necessary to make room for new ones.  The default is 10000.
+
+        @type maxAge: int
+        @param maxAge:  The number of seconds before a session expires
+        from the cache.  The default is 14400 (i.e. 4 hours)."""
+
+        self.lock = thread.allocate_lock()
+
+        # Maps sessionIDs to sessions
+        self.entriesDict = {}
+
+        #Circular list of (sessionID, timestamp) pairs
+        self.entriesList = [(None,None)] * maxEntries
+
+        self.firstIndex = 0
+        self.lastIndex = 0
+        self.maxAge = maxAge
+
+    def __getitem__(self, sessionID):
+        self.lock.acquire()
+        try:
+            self._purge() #Delete old items, so we're assured of a new one
+            session = self.entriesDict[sessionID]
+
+            #When we add sessions they're resumable, but it's possible
+            #for the session to be invalidated later on (if a fatal alert
+            #is returned), so we have to check for resumability before
+            #returning the session.
+
+            if session.valid():
+                return session
+            else:
+                raise KeyError()
+        finally:
+            self.lock.release()
+
+
+    def __setitem__(self, sessionID, session):
+        self.lock.acquire()
+        try:
+            #Add the new element
+            self.entriesDict[sessionID] = session
+            self.entriesList[self.lastIndex] = (sessionID, time.time())
+            self.lastIndex = (self.lastIndex+1) % len(self.entriesList)
+
+            #If the cache is full, we delete the oldest element to make an
+            #empty space
+            if self.lastIndex == self.firstIndex:
+                del(self.entriesDict[self.entriesList[self.firstIndex][0]])
+                self.firstIndex = (self.firstIndex+1) % len(self.entriesList)
+        finally:
+            self.lock.release()
+
+    #Delete expired items
+    def _purge(self):
+        currentTime = time.time()
+
+        #Search through the circular list, deleting expired elements until
+        #we reach a non-expired element.  Since elements in list are
+        #ordered in time, we can break once we reach the first non-expired
+        #element
+        index = self.firstIndex
+        while index != self.lastIndex:
+            if currentTime - self.entriesList[index][1] > self.maxAge:
+                del(self.entriesDict[self.entriesList[index][0]])
+                index = (index+1) % len(self.entriesList)
+            else:
+                break
+        self.firstIndex = index
+
+def _test():
+    import doctest, SessionCache
+    return doctest.testmod(SessionCache)
+
+if __name__ == "__main__":
+    _test()

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/SharedKeyDB.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/SharedKeyDB.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,58 @@
+"""Class for storing shared keys."""
+
+from utils.cryptomath import *
+from utils.compat import *
+from mathtls import *
+from Session import Session
+from BaseDB import BaseDB
+
+class SharedKeyDB(BaseDB):
+    """This class represent an in-memory or on-disk database of shared
+    keys.
+
+    A SharedKeyDB can be passed to a server handshake function to
+    authenticate a client based on one of the shared keys.
+
+    This class is thread-safe.
+    """
+
+    def __init__(self, filename=None):
+        """Create a new SharedKeyDB.
+
+        @type filename: str
+        @param filename: Filename for an on-disk database, or None for
+        an in-memory database.  If the filename already exists, follow
+        this with a call to open().  To create a new on-disk database,
+        follow this with a call to create().
+        """
+        BaseDB.__init__(self, filename, "shared key")
+
+    def _getItem(self, username, valueStr):
+        session = Session()
+        session._createSharedKey(username, valueStr)
+        return session
+
+    def __setitem__(self, username, sharedKey):
+        """Add a shared key to the database.
+
+        @type username: str
+        @param username: The username to associate the shared key with.
+        Must be less than or equal to 16 characters in length, and must
+        not already be in the database.
+
+        @type sharedKey: str
+        @param sharedKey: The shared key to add.  Must be less than 48
+        characters in length.
+        """
+        BaseDB.__setitem__(self, username, sharedKey)
+
+    def _setItem(self, username, value):
+        if len(username)>16:
+            raise ValueError("username too long")
+        if len(value)>=48:
+            raise ValueError("shared key too long")
+        return value
+
+    def _checkItem(self, value, username, param):
+        newSession = self._getItem(username, param)
+        return value.masterSecret == newSession.masterSecret
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/TLSConnection.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/TLSConnection.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,1600 @@
+"""
+MAIN CLASS FOR TLS LITE (START HERE!).
+"""
+from __future__ import generators
+
+import socket
+from utils.compat import formatExceptionTrace
+from TLSRecordLayer import TLSRecordLayer
+from Session import Session
+from constants import *
+from utils.cryptomath import getRandomBytes
+from errors import *
+from messages import *
+from mathtls import *
+from HandshakeSettings import HandshakeSettings
+
+
+class TLSConnection(TLSRecordLayer):
+    """
+    This class wraps a socket and provides TLS handshaking and data
+    transfer.
+
+    To use this class, create a new instance, passing a connected
+    socket into the constructor.  Then call some handshake function.
+    If the handshake completes without raising an exception, then a TLS
+    connection has been negotiated.  You can transfer data over this
+    connection as if it were a socket.
+
+    This class provides both synchronous and asynchronous versions of
+    its key functions.  The synchronous versions should be used when
+    writing single-or multi-threaded code using blocking sockets.  The
+    asynchronous versions should be used when performing asynchronous,
+    event-based I/O with non-blocking sockets.
+
+    Asynchronous I/O is a complicated subject; typically, you should
+    not use the asynchronous functions directly, but should use some
+    framework like asyncore or Twisted which TLS Lite integrates with
+    (see
+    L{tlslite.integration.TLSAsyncDispatcherMixIn.TLSAsyncDispatcherMixIn} or
+    L{tlslite.integration.TLSTwistedProtocolWrapper.TLSTwistedProtocolWrapper}).
+    """
+
+
+    def __init__(self, sock):
+        """Create a new TLSConnection instance.
+
+        @param sock: The socket data will be transmitted on.  The
+        socket should already be connected.  It may be in blocking or
+        non-blocking mode.
+
+        @type sock: L{socket.socket}
+        """
+        TLSRecordLayer.__init__(self, sock)
+
+    def handshakeClientSRP(self, username, password, session=None,
+                           settings=None, checker=None, async=False):
+        """Perform an SRP handshake in the role of client.
+
+        This function performs a TLS/SRP handshake.  SRP mutually
+        authenticates both parties to each other using only a
+        username and password.  This function may also perform a
+        combined SRP and server-certificate handshake, if the server
+        chooses to authenticate itself with a certificate chain in
+        addition to doing SRP.
+
+        TLS/SRP is non-standard.  Most TLS implementations don't
+        support it.  See
+        U{http://www.ietf.org/html.charters/tls-charter.html} or
+        U{http://trevp.net/tlssrp/} for the latest information on
+        TLS/SRP.
+
+        Like any handshake function, this can be called on a closed
+        TLS connection, or on a TLS connection that is already open.
+        If called on an open connection it performs a re-handshake.
+
+        If the function completes without raising an exception, the
+        TLS connection will be open and available for data transfer.
+
+        If an exception is raised, the connection will have been
+        automatically closed (if it was ever open).
+
+        @type username: str
+        @param username: The SRP username.
+
+        @type password: str
+        @param password: The SRP password.
+
+        @type session: L{tlslite.Session.Session}
+        @param session: A TLS session to attempt to resume.  This
+        session must be an SRP session performed with the same username
+        and password as were passed in.  If the resumption does not
+        succeed, a full SRP handshake will be performed.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+
+        @type checker: L{tlslite.Checker.Checker}
+        @param checker: A Checker instance.  This instance will be
+        invoked to examine the other party's authentication
+        credentials, if the handshake completes succesfully.
+
+        @type async: bool
+        @param async: If False, this function will block until the
+        handshake is completed.  If True, this function will return a
+        generator.  Successive invocations of the generator will
+        return 0 if it is waiting to read from the socket, 1 if it is
+        waiting to write to the socket, or will raise StopIteration if
+        the handshake operation is completed.
+
+        @rtype: None or an iterable
+        @return: If 'async' is True, a generator object will be
+        returned.
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        @raise tlslite.errors.TLSAuthenticationError: If the checker
+        doesn't like the other party's authentication credentials.
+        """
+        handshaker = self._handshakeClientAsync(srpParams=(username, password),
+                        session=session, settings=settings, checker=checker)
+        if async:
+            return handshaker
+        for result in handshaker:
+            pass
+
+    def handshakeClientCert(self, certChain=None, privateKey=None,
+                            session=None, settings=None, checker=None,
+                            async=False):
+        """Perform a certificate-based handshake in the role of client.
+
+        This function performs an SSL or TLS handshake.  The server
+        will authenticate itself using an X.509 or cryptoID certificate
+        chain.  If the handshake succeeds, the server's certificate
+        chain will be stored in the session's serverCertChain attribute.
+        Unless a checker object is passed in, this function does no
+        validation or checking of the server's certificate chain.
+
+        If the server requests client authentication, the
+        client will send the passed-in certificate chain, and use the
+        passed-in private key to authenticate itself.  If no
+        certificate chain and private key were passed in, the client
+        will attempt to proceed without client authentication.  The
+        server may or may not allow this.
+
+        Like any handshake function, this can be called on a closed
+        TLS connection, or on a TLS connection that is already open.
+        If called on an open connection it performs a re-handshake.
+
+        If the function completes without raising an exception, the
+        TLS connection will be open and available for data transfer.
+
+        If an exception is raised, the connection will have been
+        automatically closed (if it was ever open).
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: The certificate chain to be used if the
+        server requests client authentication.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: The private key to be used if the server
+        requests client authentication.
+
+        @type session: L{tlslite.Session.Session}
+        @param session: A TLS session to attempt to resume.  If the
+        resumption does not succeed, a full handshake will be
+        performed.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+
+        @type checker: L{tlslite.Checker.Checker}
+        @param checker: A Checker instance.  This instance will be
+        invoked to examine the other party's authentication
+        credentials, if the handshake completes succesfully.
+
+        @type async: bool
+        @param async: If False, this function will block until the
+        handshake is completed.  If True, this function will return a
+        generator.  Successive invocations of the generator will
+        return 0 if it is waiting to read from the socket, 1 if it is
+        waiting to write to the socket, or will raise StopIteration if
+        the handshake operation is completed.
+
+        @rtype: None or an iterable
+        @return: If 'async' is True, a generator object will be
+        returned.
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        @raise tlslite.errors.TLSAuthenticationError: If the checker
+        doesn't like the other party's authentication credentials.
+        """
+        handshaker = self._handshakeClientAsync(certParams=(certChain,
+                        privateKey), session=session, settings=settings,
+                        checker=checker)
+        if async:
+            return handshaker
+        for result in handshaker:
+            pass
+
+    def handshakeClientUnknown(self, srpCallback=None, certCallback=None,
+                               session=None, settings=None, checker=None,
+                               async=False):
+        """Perform a to-be-determined type of handshake in the role of client.
+
+        This function performs an SSL or TLS handshake.  If the server
+        requests client certificate authentication, the
+        certCallback will be invoked and should return a (certChain,
+        privateKey) pair.  If the callback returns None, the library
+        will attempt to proceed without client authentication.  The
+        server may or may not allow this.
+
+        If the server requests SRP authentication, the srpCallback
+        will be invoked and should return a (username, password) pair.
+        If the callback returns None, the local implementation will
+        signal a user_canceled error alert.
+
+        After the handshake completes, the client can inspect the
+        connection's session attribute to determine what type of
+        authentication was performed.
+
+        Like any handshake function, this can be called on a closed
+        TLS connection, or on a TLS connection that is already open.
+        If called on an open connection it performs a re-handshake.
+
+        If the function completes without raising an exception, the
+        TLS connection will be open and available for data transfer.
+
+        If an exception is raised, the connection will have been
+        automatically closed (if it was ever open).
+
+        @type srpCallback: callable
+        @param srpCallback: The callback to be used if the server
+        requests SRP authentication.  If None, the client will not
+        offer support for SRP ciphersuites.
+
+        @type certCallback: callable
+        @param certCallback: The callback to be used if the server
+        requests client certificate authentication.
+
+        @type session: L{tlslite.Session.Session}
+        @param session: A TLS session to attempt to resume.  If the
+        resumption does not succeed, a full handshake will be
+        performed.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+
+        @type checker: L{tlslite.Checker.Checker}
+        @param checker: A Checker instance.  This instance will be
+        invoked to examine the other party's authentication
+        credentials, if the handshake completes succesfully.
+
+        @type async: bool
+        @param async: If False, this function will block until the
+        handshake is completed.  If True, this function will return a
+        generator.  Successive invocations of the generator will
+        return 0 if it is waiting to read from the socket, 1 if it is
+        waiting to write to the socket, or will raise StopIteration if
+        the handshake operation is completed.
+
+        @rtype: None or an iterable
+        @return: If 'async' is True, a generator object will be
+        returned.
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        @raise tlslite.errors.TLSAuthenticationError: If the checker
+        doesn't like the other party's authentication credentials.
+        """
+        handshaker = self._handshakeClientAsync(unknownParams=(srpCallback,
+                        certCallback), session=session, settings=settings,
+                        checker=checker)
+        if async:
+            return handshaker
+        for result in handshaker:
+            pass
+
+    def handshakeClientSharedKey(self, username, sharedKey, settings=None,
+                                 checker=None, async=False):
+        """Perform a shared-key handshake in the role of client.
+
+        This function performs a shared-key handshake.  Using shared
+        symmetric keys of high entropy (128 bits or greater) mutually
+        authenticates both parties to each other.
+
+        TLS with shared-keys is non-standard.  Most TLS
+        implementations don't support it.  See
+        U{http://www.ietf.org/html.charters/tls-charter.html} for the
+        latest information on TLS with shared-keys.  If the shared-keys
+        Internet-Draft changes or is superceded, TLS Lite will track
+        those changes, so the shared-key support in later versions of
+        TLS Lite may become incompatible with this version.
+
+        Like any handshake function, this can be called on a closed
+        TLS connection, or on a TLS connection that is already open.
+        If called on an open connection it performs a re-handshake.
+
+        If the function completes without raising an exception, the
+        TLS connection will be open and available for data transfer.
+
+        If an exception is raised, the connection will have been
+        automatically closed (if it was ever open).
+
+        @type username: str
+        @param username: The shared-key username.
+
+        @type sharedKey: str
+        @param sharedKey: The shared key.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+
+        @type checker: L{tlslite.Checker.Checker}
+        @param checker: A Checker instance.  This instance will be
+        invoked to examine the other party's authentication
+        credentials, if the handshake completes succesfully.
+
+        @type async: bool
+        @param async: If False, this function will block until the
+        handshake is completed.  If True, this function will return a
+        generator.  Successive invocations of the generator will
+        return 0 if it is waiting to read from the socket, 1 if it is
+        waiting to write to the socket, or will raise StopIteration if
+        the handshake operation is completed.
+
+        @rtype: None or an iterable
+        @return: If 'async' is True, a generator object will be
+        returned.
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        @raise tlslite.errors.TLSAuthenticationError: If the checker
+        doesn't like the other party's authentication credentials.
+        """
+        handshaker = self._handshakeClientAsync(sharedKeyParams=(username,
+                        sharedKey), settings=settings, checker=checker)
+        if async:
+            return handshaker
+        for result in handshaker:
+            pass
+
+    def _handshakeClientAsync(self, srpParams=(), certParams=(),
+                             unknownParams=(), sharedKeyParams=(),
+                             session=None, settings=None, checker=None,
+                             recursive=False):
+
+        handshaker = self._handshakeClientAsyncHelper(srpParams=srpParams,
+                certParams=certParams, unknownParams=unknownParams,
+                sharedKeyParams=sharedKeyParams, session=session,
+                settings=settings, recursive=recursive)
+        for result in self._handshakeWrapperAsync(handshaker, checker):
+            yield result
+
+
+    def _handshakeClientAsyncHelper(self, srpParams, certParams, unknownParams,
+                               sharedKeyParams, session, settings, recursive):
+        if not recursive:
+            self._handshakeStart(client=True)
+
+        #Unpack parameters
+        srpUsername = None      # srpParams
+        password = None         # srpParams
+        clientCertChain = None  # certParams
+        privateKey = None       # certParams
+        srpCallback = None      # unknownParams
+        certCallback = None     # unknownParams
+        #session                # sharedKeyParams (or session)
+        #settings               # settings
+
+        if srpParams:
+            srpUsername, password = srpParams
+        elif certParams:
+            clientCertChain, privateKey = certParams
+        elif unknownParams:
+            srpCallback, certCallback = unknownParams
+        elif sharedKeyParams:
+            session = Session()._createSharedKey(*sharedKeyParams)
+
+        if not settings:
+            settings = HandshakeSettings()
+        settings = settings._filter()
+
+        #Validate parameters
+        if srpUsername and not password:
+            raise ValueError("Caller passed a username but no password")
+        if password and not srpUsername:
+            raise ValueError("Caller passed a password but no username")
+
+        if clientCertChain and not privateKey:
+            raise ValueError("Caller passed a certChain but no privateKey")
+        if privateKey and not clientCertChain:
+            raise ValueError("Caller passed a privateKey but no certChain")
+
+        if clientCertChain:
+            foundType = False
+            try:
+                import cryptoIDlib.CertChain
+                if isinstance(clientCertChain, cryptoIDlib.CertChain.CertChain):
+                    if "cryptoID" not in settings.certificateTypes:
+                        raise ValueError("Client certificate doesn't "\
+                                         "match Handshake Settings")
+                    settings.certificateTypes = ["cryptoID"]
+                    foundType = True
+            except ImportError:
+                pass
+            if not foundType and isinstance(clientCertChain,
+                                            X509CertChain):
+                if "x509" not in settings.certificateTypes:
+                    raise ValueError("Client certificate doesn't match "\
+                                     "Handshake Settings")
+                settings.certificateTypes = ["x509"]
+                foundType = True
+            if not foundType:
+                raise ValueError("Unrecognized certificate type")
+
+
+        if session:
+            if not session.valid():
+                session = None #ignore non-resumable sessions...
+            elif session.resumable and \
+                    (session.srpUsername != srpUsername):
+                raise ValueError("Session username doesn't match")
+
+        #Add Faults to parameters
+        if srpUsername and self.fault == Fault.badUsername:
+            srpUsername += "GARBAGE"
+        if password and self.fault == Fault.badPassword:
+            password += "GARBAGE"
+        if sharedKeyParams:
+            identifier = sharedKeyParams[0]
+            sharedKey = sharedKeyParams[1]
+            if self.fault == Fault.badIdentifier:
+                identifier += "GARBAGE"
+                session = Session()._createSharedKey(identifier, sharedKey)
+            elif self.fault == Fault.badSharedKey:
+                sharedKey += "GARBAGE"
+                session = Session()._createSharedKey(identifier, sharedKey)
+
+
+        #Initialize locals
+        serverCertChain = None
+        cipherSuite = 0
+        certificateType = CertificateType.x509
+        premasterSecret = None
+
+        #Get client nonce
+        clientRandom = getRandomBytes(32)
+
+        #Initialize acceptable ciphersuites
+        cipherSuites = []
+        if srpParams:
+            cipherSuites += CipherSuite.getSrpRsaSuites(settings.cipherNames)
+            cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames)
+        elif certParams:
+            cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
+        elif unknownParams:
+            if srpCallback:
+                cipherSuites += \
+                    CipherSuite.getSrpRsaSuites(settings.cipherNames)
+                cipherSuites += \
+                    CipherSuite.getSrpSuites(settings.cipherNames)
+            cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
+        elif sharedKeyParams:
+            cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
+        else:
+            cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
+
+        #Initialize acceptable certificate types
+        certificateTypes = settings._getCertificateTypes()
+
+        #Tentatively set the version to the client's minimum version.
+        #We'll use this for the ClientHello, and if an error occurs
+        #parsing the Server Hello, we'll use this version for the response
+        self.version = settings.maxVersion
+
+        #Either send ClientHello (with a resumable session)...
+        if session:
+            #If it's a resumable (i.e. not a shared-key session), then its
+            #ciphersuite must be one of the acceptable ciphersuites
+            if (not sharedKeyParams) and \
+                session.cipherSuite not in cipherSuites:
+                raise ValueError("Session's cipher suite not consistent "\
+                                 "with parameters")
+            else:
+                clientHello = ClientHello()
+                clientHello.create(settings.maxVersion, clientRandom,
+                                   session.sessionID, cipherSuites,
+                                   certificateTypes, session.srpUsername)
+
+        #Or send ClientHello (without)
+        else:
+            clientHello = ClientHello()
+            clientHello.create(settings.maxVersion, clientRandom,
+                               createByteArraySequence([]), cipherSuites,
+                               certificateTypes, srpUsername)
+        for result in self._sendMsg(clientHello):
+            yield result
+
+        #Get ServerHello (or missing_srp_username)
+        for result in self._getMsg((ContentType.handshake,
+                                  ContentType.alert),
+                                  HandshakeType.server_hello):
+            if result in (0,1):
+                yield result
+            else:
+                break
+        msg = result
+
+        if isinstance(msg, ServerHello):
+            serverHello = msg
+        elif isinstance(msg, Alert):
+            alert = msg
+
+            #If it's not a missing_srp_username, re-raise
+            if alert.description != AlertDescription.missing_srp_username:
+                self._shutdown(False)
+                raise TLSRemoteAlert(alert)
+
+            #If we're not in SRP callback mode, we won't have offered SRP
+            #without a username, so we shouldn't get this alert
+            if not srpCallback:
+                for result in self._sendError(\
+                                AlertDescription.unexpected_message):
+                    yield result
+            srpParams = srpCallback()
+            #If the callback returns None, cancel the handshake
+            if srpParams == None:
+                for result in self._sendError(AlertDescription.user_canceled):
+                    yield result
+
+            #Recursively perform handshake
+            for result in self._handshakeClientAsyncHelper(srpParams,
+                            None, None, None, None, settings, True):
+                yield result
+            return
+
+        #Get the server version.  Do this before anything else, so any
+        #error alerts will use the server's version
+        self.version = serverHello.server_version
+
+        #Future responses from server must use this version
+        self._versionCheck = True
+
+        #Check ServerHello
+        if serverHello.server_version < settings.minVersion:
+            for result in self._sendError(\
+                AlertDescription.protocol_version,
+                "Too old version: %s" % str(serverHello.server_version)):
+                yield result
+        if serverHello.server_version > settings.maxVersion:
+            for result in self._sendError(\
+                AlertDescription.protocol_version,
+                "Too new version: %s" % str(serverHello.server_version)):
+                yield result
+        if serverHello.cipher_suite not in cipherSuites:
+            for result in self._sendError(\
+                AlertDescription.illegal_parameter,
+                "Server responded with incorrect ciphersuite"):
+                yield result
+        if serverHello.certificate_type not in certificateTypes:
+            for result in self._sendError(\
+                AlertDescription.illegal_parameter,
+                "Server responded with incorrect certificate type"):
+                yield result
+        if serverHello.compression_method != 0:
+            for result in self._sendError(\
+                AlertDescription.illegal_parameter,
+                "Server responded with incorrect compression method"):
+                yield result
+
+        #Get the server nonce
+        serverRandom = serverHello.random
+
+        #If the server agrees to resume
+        if session and session.sessionID and \
+                       serverHello.session_id == session.sessionID:
+
+            #If a shared-key, we're flexible about suites; otherwise the
+            #server-chosen suite has to match the session's suite
+            if sharedKeyParams:
+                session.cipherSuite = serverHello.cipher_suite
+            elif serverHello.cipher_suite != session.cipherSuite:
+                for result in self._sendError(\
+                    AlertDescription.illegal_parameter,\
+                    "Server's ciphersuite doesn't match session"):
+                    yield result
+
+            #Set the session for this connection
+            self.session = session
+
+            #Calculate pending connection states
+            self._calcPendingStates(clientRandom, serverRandom,
+                                   settings.cipherImplementations)
+
+            #Exchange ChangeCipherSpec and Finished messages
+            for result in self._getFinished():
+                yield result
+            for result in self._sendFinished():
+                yield result
+
+            #Mark the connection as open
+            self._handshakeDone(resumed=True)
+
+        #If server DOES NOT agree to resume
+        else:
+
+            if sharedKeyParams:
+                for result in self._sendError(\
+                        AlertDescription.user_canceled,
+                        "Was expecting a shared-key resumption"):
+                    yield result
+
+            #We've already validated these
+            cipherSuite = serverHello.cipher_suite
+            certificateType = serverHello.certificate_type
+
+            #If the server chose an SRP suite...
+            if cipherSuite in CipherSuite.srpSuites:
+                #Get ServerKeyExchange, ServerHelloDone
+                for result in self._getMsg(ContentType.handshake,
+                        HandshakeType.server_key_exchange, cipherSuite):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                serverKeyExchange = result
+
+                for result in self._getMsg(ContentType.handshake,
+                        HandshakeType.server_hello_done):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                serverHelloDone = result
+
+            #If the server chose an SRP+RSA suite...
+            elif cipherSuite in CipherSuite.srpRsaSuites:
+                #Get Certificate, ServerKeyExchange, ServerHelloDone
+                for result in self._getMsg(ContentType.handshake,
+                        HandshakeType.certificate, certificateType):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                serverCertificate = result
+
+                for result in self._getMsg(ContentType.handshake,
+                        HandshakeType.server_key_exchange, cipherSuite):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                serverKeyExchange = result
+
+                for result in self._getMsg(ContentType.handshake,
+                        HandshakeType.server_hello_done):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                serverHelloDone = result
+
+            #If the server chose an RSA suite...
+            elif cipherSuite in CipherSuite.rsaSuites:
+                #Get Certificate[, CertificateRequest], ServerHelloDone
+                for result in self._getMsg(ContentType.handshake,
+                        HandshakeType.certificate, certificateType):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                serverCertificate = result
+
+                for result in self._getMsg(ContentType.handshake,
+                        (HandshakeType.server_hello_done,
+                        HandshakeType.certificate_request)):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                msg = result
+
+                certificateRequest = None
+                if isinstance(msg, CertificateRequest):
+                    certificateRequest = msg
+                    for result in self._getMsg(ContentType.handshake,
+                            HandshakeType.server_hello_done):
+                        if result in (0,1):
+                            yield result
+                        else:
+                            break
+                    serverHelloDone = result
+                elif isinstance(msg, ServerHelloDone):
+                    serverHelloDone = msg
+            else:
+                raise AssertionError()
+
+
+            #Calculate SRP premaster secret, if server chose an SRP or
+            #SRP+RSA suite
+            if cipherSuite in CipherSuite.srpSuites + \
+                              CipherSuite.srpRsaSuites:
+                #Get and check the server's group parameters and B value
+                N = serverKeyExchange.srp_N
+                g = serverKeyExchange.srp_g
+                s = serverKeyExchange.srp_s
+                B = serverKeyExchange.srp_B
+
+                if (g,N) not in goodGroupParameters:
+                    for result in self._sendError(\
+                            AlertDescription.untrusted_srp_parameters,
+                            "Unknown group parameters"):
+                        yield result
+                if numBits(N) < settings.minKeySize:
+                    for result in self._sendError(\
+                            AlertDescription.untrusted_srp_parameters,
+                            "N value is too small: %d" % numBits(N)):
+                        yield result
+                if numBits(N) > settings.maxKeySize:
+                    for result in self._sendError(\
+                            AlertDescription.untrusted_srp_parameters,
+                            "N value is too large: %d" % numBits(N)):
+                        yield result
+                if B % N == 0:
+                    for result in self._sendError(\
+                            AlertDescription.illegal_parameter,
+                            "Suspicious B value"):
+                        yield result
+
+                #Check the server's signature, if server chose an
+                #SRP+RSA suite
+                if cipherSuite in CipherSuite.srpRsaSuites:
+                    #Hash ServerKeyExchange/ServerSRPParams
+                    hashBytes = serverKeyExchange.hash(clientRandom,
+                                                       serverRandom)
+
+                    #Extract signature bytes from ServerKeyExchange
+                    sigBytes = serverKeyExchange.signature
+                    if len(sigBytes) == 0:
+                        for result in self._sendError(\
+                                AlertDescription.illegal_parameter,
+                                "Server sent an SRP ServerKeyExchange "\
+                                "message without a signature"):
+                            yield result
+
+                    #Get server's public key from the Certificate message
+                    for result in self._getKeyFromChain(serverCertificate,
+                                                       settings):
+                        if result in (0,1):
+                            yield result
+                        else:
+                            break
+                    publicKey, serverCertChain = result
+
+                    #Verify signature
+                    if not publicKey.verify(sigBytes, hashBytes):
+                        for result in self._sendError(\
+                                AlertDescription.decrypt_error,
+                                "Signature failed to verify"):
+                            yield result
+
+
+                #Calculate client's ephemeral DH values (a, A)
+                a = bytesToNumber(getRandomBytes(32))
+                A = powMod(g, a, N)
+
+                #Calculate client's static DH values (x, v)
+                x = makeX(bytesToString(s), srpUsername, password)
+                v = powMod(g, x, N)
+
+                #Calculate u
+                u = makeU(N, A, B)
+
+                #Calculate premaster secret
+                k = makeK(N, g)
+                S = powMod((B - (k*v)) % N, a+(u*x), N)
+
+                if self.fault == Fault.badA:
+                    A = N
+                    S = 0
+                premasterSecret = numberToBytes(S)
+
+                #Send ClientKeyExchange
+                for result in self._sendMsg(\
+                        ClientKeyExchange(cipherSuite).createSRP(A)):
+                    yield result
+
+
+            #Calculate RSA premaster secret, if server chose an RSA suite
+            elif cipherSuite in CipherSuite.rsaSuites:
+
+                #Handle the presence of a CertificateRequest
+                if certificateRequest:
+                    if unknownParams and certCallback:
+                        certParamsNew = certCallback()
+                        if certParamsNew:
+                            clientCertChain, privateKey = certParamsNew
+
+                #Get server's public key from the Certificate message
+                for result in self._getKeyFromChain(serverCertificate,
+                                                   settings):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                publicKey, serverCertChain = result
+
+
+                #Calculate premaster secret
+                premasterSecret = getRandomBytes(48)
+                premasterSecret[0] = settings.maxVersion[0]
+                premasterSecret[1] = settings.maxVersion[1]
+
+                if self.fault == Fault.badPremasterPadding:
+                    premasterSecret[0] = 5
+                if self.fault == Fault.shortPremasterSecret:
+                    premasterSecret = premasterSecret[:-1]
+
+                #Encrypt premaster secret to server's public key
+                encryptedPreMasterSecret = publicKey.encrypt(premasterSecret)
+
+                #If client authentication was requested, send Certificate
+                #message, either with certificates or empty
+                if certificateRequest:
+                    clientCertificate = Certificate(certificateType)
+
+                    if clientCertChain:
+                        #Check to make sure we have the same type of
+                        #certificates the server requested
+                        wrongType = False
+                        if certificateType == CertificateType.x509:
+                            if not isinstance(clientCertChain, X509CertChain):
+                                wrongType = True
+                        elif certificateType == CertificateType.cryptoID:
+                            if not isinstance(clientCertChain,
+                                              cryptoIDlib.CertChain.CertChain):
+                                wrongType = True
+                        if wrongType:
+                            for result in self._sendError(\
+                                    AlertDescription.handshake_failure,
+                                    "Client certificate is of wrong type"):
+                                yield result
+
+                        clientCertificate.create(clientCertChain)
+
+                    for result in self._sendMsg(clientCertificate):
+                        yield result
+                else:
+                    #The server didn't request client auth, so we
+                    #zeroize these so the clientCertChain won't be
+                    #stored in the session.
+                    privateKey = None
+                    clientCertChain = None
+
+                #Send ClientKeyExchange
+                clientKeyExchange = ClientKeyExchange(cipherSuite,
+                                                      self.version)
+                clientKeyExchange.createRSA(encryptedPreMasterSecret)
+                for result in self._sendMsg(clientKeyExchange):
+                    yield result
+
+                #If client authentication was requested and we have a
+                #private key, send CertificateVerify
+                if certificateRequest and privateKey:
+                    if self.version == (3,0):
+                        #Create a temporary session object, just for the
+                        #purpose of creating the CertificateVerify
+                        session = Session()
+                        session._calcMasterSecret(self.version,
+                                                 premasterSecret,
+                                                 clientRandom,
+                                                 serverRandom)
+                        verifyBytes = self._calcSSLHandshakeHash(\
+                                          session.masterSecret, "")
+                    elif self.version in ((3,1), (3,2)):
+                        verifyBytes = stringToBytes(\
+                            self._handshake_md5.digest() + \
+                            self._handshake_sha.digest())
+                    if self.fault == Fault.badVerifyMessage:
+                        verifyBytes[0] = ((verifyBytes[0]+1) % 256)
+                    signedBytes = privateKey.sign(verifyBytes)
+                    certificateVerify = CertificateVerify()
+                    certificateVerify.create(signedBytes)
+                    for result in self._sendMsg(certificateVerify):
+                        yield result
+
+
+            #Create the session object
+            self.session = Session()
+            self.session._calcMasterSecret(self.version, premasterSecret,
+                                          clientRandom, serverRandom)
+            self.session.sessionID = serverHello.session_id
+            self.session.cipherSuite = cipherSuite
+            self.session.srpUsername = srpUsername
+            self.session.clientCertChain = clientCertChain
+            self.session.serverCertChain = serverCertChain
+
+            #Calculate pending connection states
+            self._calcPendingStates(clientRandom, serverRandom,
+                                   settings.cipherImplementations)
+
+            #Exchange ChangeCipherSpec and Finished messages
+            for result in self._sendFinished():
+                yield result
+            for result in self._getFinished():
+                yield result
+
+            #Mark the connection as open
+            self.session._setResumable(True)
+            self._handshakeDone(resumed=False)
+
+
+
+    def handshakeServer(self, sharedKeyDB=None, verifierDB=None,
+                        certChain=None, privateKey=None, reqCert=False,
+                        sessionCache=None, settings=None, checker=None):
+        """Perform a handshake in the role of server.
+
+        This function performs an SSL or TLS handshake.  Depending on
+        the arguments and the behavior of the client, this function can
+        perform a shared-key, SRP, or certificate-based handshake.  It
+        can also perform a combined SRP and server-certificate
+        handshake.
+
+        Like any handshake function, this can be called on a closed
+        TLS connection, or on a TLS connection that is already open.
+        If called on an open connection it performs a re-handshake.
+        This function does not send a Hello Request message before
+        performing the handshake, so if re-handshaking is required,
+        the server must signal the client to begin the re-handshake
+        through some other means.
+
+        If the function completes without raising an exception, the
+        TLS connection will be open and available for data transfer.
+
+        If an exception is raised, the connection will have been
+        automatically closed (if it was ever open).
+
+        @type sharedKeyDB: L{tlslite.SharedKeyDB.SharedKeyDB}
+        @param sharedKeyDB: A database of shared symmetric keys
+        associated with usernames.  If the client performs a
+        shared-key handshake, the session's sharedKeyUsername
+        attribute will be set.
+
+        @type verifierDB: L{tlslite.VerifierDB.VerifierDB}
+        @param verifierDB: A database of SRP password verifiers
+        associated with usernames.  If the client performs an SRP
+        handshake, the session's srpUsername attribute will be set.
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: The certificate chain to be used if the
+        client requests server certificate authentication.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: The private key to be used if the client
+        requests server certificate authentication.
+
+        @type reqCert: bool
+        @param reqCert: Whether to request client certificate
+        authentication.  This only applies if the client chooses server
+        certificate authentication; if the client chooses SRP or
+        shared-key authentication, this will be ignored.  If the client
+        performs a client certificate authentication, the sessions's
+        clientCertChain attribute will be set.
+
+        @type sessionCache: L{tlslite.SessionCache.SessionCache}
+        @param sessionCache: An in-memory cache of resumable sessions.
+        The client can resume sessions from this cache.  Alternatively,
+        if the client performs a full handshake, a new session will be
+        added to the cache.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites and SSL/TLS version chosen by the server.
+
+        @type checker: L{tlslite.Checker.Checker}
+        @param checker: A Checker instance.  This instance will be
+        invoked to examine the other party's authentication
+        credentials, if the handshake completes succesfully.
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        @raise tlslite.errors.TLSAuthenticationError: If the checker
+        doesn't like the other party's authentication credentials.
+        """
+        for result in self.handshakeServerAsync(sharedKeyDB, verifierDB,
+                certChain, privateKey, reqCert, sessionCache, settings,
+                checker):
+            pass
+
+
+    def handshakeServerAsync(self, sharedKeyDB=None, verifierDB=None,
+                             certChain=None, privateKey=None, reqCert=False,
+                             sessionCache=None, settings=None, checker=None):
+        """Start a server handshake operation on the TLS connection.
+
+        This function returns a generator which behaves similarly to
+        handshakeServer().  Successive invocations of the generator
+        will return 0 if it is waiting to read from the socket, 1 if it is
+        waiting to write to the socket, or it will raise StopIteration
+        if the handshake operation is complete.
+
+        @rtype: iterable
+        @return: A generator; see above for details.
+        """
+        handshaker = self._handshakeServerAsyncHelper(\
+            sharedKeyDB=sharedKeyDB,
+            verifierDB=verifierDB, certChain=certChain,
+            privateKey=privateKey, reqCert=reqCert,
+            sessionCache=sessionCache, settings=settings)
+        for result in self._handshakeWrapperAsync(handshaker, checker):
+            yield result
+
+
+    def _handshakeServerAsyncHelper(self, sharedKeyDB, verifierDB,
+                             certChain, privateKey, reqCert, sessionCache,
+                             settings):
+
+        self._handshakeStart(client=False)
+
+        if (not sharedKeyDB) and (not verifierDB) and (not certChain):
+            raise ValueError("Caller passed no authentication credentials")
+        if certChain and not privateKey:
+            raise ValueError("Caller passed a certChain but no privateKey")
+        if privateKey and not certChain:
+            raise ValueError("Caller passed a privateKey but no certChain")
+
+        if not settings:
+            settings = HandshakeSettings()
+        settings = settings._filter()
+
+        #Initialize acceptable cipher suites
+        cipherSuites = []
+        if verifierDB:
+            if certChain:
+                cipherSuites += \
+                    CipherSuite.getSrpRsaSuites(settings.cipherNames)
+            cipherSuites += CipherSuite.getSrpSuites(settings.cipherNames)
+        if sharedKeyDB or certChain:
+            cipherSuites += CipherSuite.getRsaSuites(settings.cipherNames)
+
+        #Initialize acceptable certificate type
+        certificateType = None
+        if certChain:
+            try:
+                import cryptoIDlib.CertChain
+                if isinstance(certChain, cryptoIDlib.CertChain.CertChain):
+                    certificateType = CertificateType.cryptoID
+            except ImportError:
+                pass
+            if isinstance(certChain, X509CertChain):
+                certificateType = CertificateType.x509
+            if certificateType == None:
+                raise ValueError("Unrecognized certificate type")
+
+        #Initialize locals
+        clientCertChain = None
+        serverCertChain = None #We may set certChain to this later
+        postFinishedError = None
+
+        #Tentatively set version to most-desirable version, so if an error
+        #occurs parsing the ClientHello, this is what we'll use for the
+        #error alert
+        self.version = settings.maxVersion
+
+        #Get ClientHello
+        for result in self._getMsg(ContentType.handshake,
+                                   HandshakeType.client_hello):
+            if result in (0,1):
+                yield result
+            else:
+                break
+        clientHello = result
+
+        #If client's version is too low, reject it
+        if clientHello.client_version < settings.minVersion:
+            self.version = settings.minVersion
+            for result in self._sendError(\
+                  AlertDescription.protocol_version,
+                  "Too old version: %s" % str(clientHello.client_version)):
+                yield result
+
+        #If client's version is too high, propose my highest version
+        elif clientHello.client_version > settings.maxVersion:
+            self.version = settings.maxVersion
+
+        else:
+            #Set the version to the client's version
+            self.version = clientHello.client_version
+
+        #Get the client nonce; create server nonce
+        clientRandom = clientHello.random
+        serverRandom = getRandomBytes(32)
+
+        #Calculate the first cipher suite intersection.
+        #This is the 'privileged' ciphersuite.  We'll use it if we're
+        #doing a shared-key resumption or a new negotiation.  In fact,
+        #the only time we won't use it is if we're resuming a non-sharedkey
+        #session, in which case we use the ciphersuite from the session.
+        #
+        #Given the current ciphersuite ordering, this means we prefer SRP
+        #over non-SRP.
+        for cipherSuite in cipherSuites:
+            if cipherSuite in clientHello.cipher_suites:
+                break
+        else:
+            for result in self._sendError(\
+                    AlertDescription.handshake_failure):
+                yield result
+
+        #If resumption was requested...
+        if clientHello.session_id and (sharedKeyDB or sessionCache):
+            session = None
+
+            #Check in the sharedKeys container
+            if sharedKeyDB and len(clientHello.session_id)==16:
+                try:
+                    #Trim off zero padding, if any
+                    for x in range(16):
+                        if clientHello.session_id[x]==0:
+                            break
+                    self.allegedSharedKeyUsername = bytesToString(\
+                                            clientHello.session_id[:x])
+                    session = sharedKeyDB[self.allegedSharedKeyUsername]
+                    if not session.sharedKey:
+                        raise AssertionError()
+                    #use privileged ciphersuite
+                    session.cipherSuite = cipherSuite
+                except KeyError:
+                    pass
+
+            #Then check in the session cache
+            if sessionCache and not session:
+                try:
+                    session = sessionCache[bytesToString(\
+                                               clientHello.session_id)]
+                    if session.sharedKey:
+                        raise AssertionError()
+                    if not session.resumable:
+                        raise AssertionError()
+                    #Check for consistency with ClientHello
+                    if session.cipherSuite not in cipherSuites:
+                        for result in self._sendError(\
+                                AlertDescription.handshake_failure):
+                            yield result
+                    if session.cipherSuite not in clientHello.cipher_suites:
+                        for result in self._sendError(\
+                                AlertDescription.handshake_failure):
+                            yield result
+                    if clientHello.srp_username:
+                        if clientHello.srp_username != session.srpUsername:
+                            for result in self._sendError(\
+                                    AlertDescription.handshake_failure):
+                                yield result
+                except KeyError:
+                    pass
+
+            #If a session is found..
+            if session:
+                #Set the session
+                self.session = session
+
+                #Send ServerHello
+                serverHello = ServerHello()
+                serverHello.create(self.version, serverRandom,
+                                   session.sessionID, session.cipherSuite,
+                                   certificateType)
+                for result in self._sendMsg(serverHello):
+                    yield result
+
+                #From here on, the client's messages must have the right version
+                self._versionCheck = True
+
+                #Calculate pending connection states
+                self._calcPendingStates(clientRandom, serverRandom,
+                                       settings.cipherImplementations)
+
+                #Exchange ChangeCipherSpec and Finished messages
+                for result in self._sendFinished():
+                    yield result
+                for result in self._getFinished():
+                    yield result
+
+                #Mark the connection as open
+                self._handshakeDone(resumed=True)
+                return
+
+
+        #If not a resumption...
+
+        #TRICKY: we might have chosen an RSA suite that was only deemed
+        #acceptable because of the shared-key resumption.  If the shared-
+        #key resumption failed, because the identifier wasn't recognized,
+        #we might fall through to here, where we have an RSA suite
+        #chosen, but no certificate.
+        if cipherSuite in CipherSuite.rsaSuites and not certChain:
+            for result in self._sendError(\
+                    AlertDescription.handshake_failure):
+                yield result
+
+        #If an RSA suite is chosen, check for certificate type intersection
+        #(We do this check down here because if the mismatch occurs but the
+        # client is using a shared-key session, it's okay)
+        if cipherSuite in CipherSuite.rsaSuites + \
+                          CipherSuite.srpRsaSuites:
+            if certificateType not in clientHello.certificate_types:
+                for result in self._sendError(\
+                        AlertDescription.handshake_failure,
+                        "the client doesn't support my certificate type"):
+                    yield result
+
+            #Move certChain -> serverCertChain, now that we're using it
+            serverCertChain = certChain
+
+
+        #Create sessionID
+        if sessionCache:
+            sessionID = getRandomBytes(32)
+        else:
+            sessionID = createByteArraySequence([])
+
+        #If we've selected an SRP suite, exchange keys and calculate
+        #premaster secret:
+        if cipherSuite in CipherSuite.srpSuites + CipherSuite.srpRsaSuites:
+
+            #If there's no SRP username...
+            if not clientHello.srp_username:
+
+                #Ask the client to re-send ClientHello with one
+                for result in self._sendMsg(Alert().create(\
+                        AlertDescription.missing_srp_username,
+                        AlertLevel.warning)):
+                    yield result
+
+                #Get ClientHello
+                for result in self._getMsg(ContentType.handshake,
+                        HandshakeType.client_hello):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                clientHello = result
+
+                #Check ClientHello
+                #If client's version is too low, reject it (COPIED CODE; BAD!)
+                if clientHello.client_version < settings.minVersion:
+                    self.version = settings.minVersion
+                    for result in self._sendError(\
+                          AlertDescription.protocol_version,
+                          "Too old version: %s" % str(clientHello.client_version)):
+                        yield result
+
+                #If client's version is too high, propose my highest version
+                elif clientHello.client_version > settings.maxVersion:
+                    self.version = settings.maxVersion
+
+                else:
+                    #Set the version to the client's version
+                    self.version = clientHello.client_version
+
+                #Recalculate the privileged cipher suite, making sure to
+                #pick an SRP suite
+                cipherSuites = [c for c in cipherSuites if c in \
+                                CipherSuite.srpSuites + \
+                                CipherSuite.srpRsaSuites]
+                for cipherSuite in cipherSuites:
+                    if cipherSuite in clientHello.cipher_suites:
+                        break
+                else:
+                    for result in self._sendError(\
+                            AlertDescription.handshake_failure):
+                        yield result
+
+                #Get the client nonce; create server nonce
+                clientRandom = clientHello.random
+                serverRandom = getRandomBytes(32)
+
+                #The username better be there, this time
+                if not clientHello.srp_username:
+                    for result in self._sendError(\
+                            AlertDescription.illegal_parameter,
+                            "Client resent a hello, but without the SRP"\
+                            " username"):
+                        yield result
+
+
+            #Get username
+            self.allegedSrpUsername = clientHello.srp_username
+
+            #Get parameters from username
+            try:
+                entry = verifierDB[self.allegedSrpUsername]
+            except KeyError:
+                for result in self._sendError(\
+                        AlertDescription.unknown_srp_username):
+                    yield result
+            (N, g, s, v) = entry
+
+            #Calculate server's ephemeral DH values (b, B)
+            b = bytesToNumber(getRandomBytes(32))
+            k = makeK(N, g)
+            B = (powMod(g, b, N) + (k*v)) % N
+
+            #Create ServerKeyExchange, signing it if necessary
+            serverKeyExchange = ServerKeyExchange(cipherSuite)
+            serverKeyExchange.createSRP(N, g, stringToBytes(s), B)
+            if cipherSuite in CipherSuite.srpRsaSuites:
+                hashBytes = serverKeyExchange.hash(clientRandom,
+                                                   serverRandom)
+                serverKeyExchange.signature = privateKey.sign(hashBytes)
+
+            #Send ServerHello[, Certificate], ServerKeyExchange,
+            #ServerHelloDone
+            msgs = []
+            serverHello = ServerHello()
+            serverHello.create(self.version, serverRandom, sessionID,
+                               cipherSuite, certificateType)
+            msgs.append(serverHello)
+            if cipherSuite in CipherSuite.srpRsaSuites:
+                certificateMsg = Certificate(certificateType)
+                certificateMsg.create(serverCertChain)
+                msgs.append(certificateMsg)
+            msgs.append(serverKeyExchange)
+            msgs.append(ServerHelloDone())
+            for result in self._sendMsgs(msgs):
+                yield result
+
+            #From here on, the client's messages must have the right version
+            self._versionCheck = True
+
+            #Get and check ClientKeyExchange
+            for result in self._getMsg(ContentType.handshake,
+                                      HandshakeType.client_key_exchange,
+                                      cipherSuite):
+                if result in (0,1):
+                    yield result
+                else:
+                    break
+            clientKeyExchange = result
+            A = clientKeyExchange.srp_A
+            if A % N == 0:
+                postFinishedError = (AlertDescription.illegal_parameter,
+                                     "Suspicious A value")
+            #Calculate u
+            u = makeU(N, A, B)
+
+            #Calculate premaster secret
+            S = powMod((A * powMod(v,u,N)) % N, b, N)
+            premasterSecret = numberToBytes(S)
+
+
+        #If we've selected an RSA suite, exchange keys and calculate
+        #premaster secret:
+        elif cipherSuite in CipherSuite.rsaSuites:
+
+            #Send ServerHello, Certificate[, CertificateRequest],
+            #ServerHelloDone
+            msgs = []
+            msgs.append(ServerHello().create(self.version, serverRandom,
+                        sessionID, cipherSuite, certificateType))
+            msgs.append(Certificate(certificateType).create(serverCertChain))
+            if reqCert:
+                msgs.append(CertificateRequest())
+            msgs.append(ServerHelloDone())
+            for result in self._sendMsgs(msgs):
+                yield result
+
+            #From here on, the client's messages must have the right version
+            self._versionCheck = True
+
+            #Get [Certificate,] (if was requested)
+            if reqCert:
+                if self.version == (3,0):
+                    for result in self._getMsg((ContentType.handshake,
+                                               ContentType.alert),
+                                               HandshakeType.certificate,
+                                               certificateType):
+                        if result in (0,1):
+                            yield result
+                        else:
+                            break
+                    msg = result
+
+                    if isinstance(msg, Alert):
+                        #If it's not a no_certificate alert, re-raise
+                        alert = msg
+                        if alert.description != \
+                                AlertDescription.no_certificate:
+                            self._shutdown(False)
+                            raise TLSRemoteAlert(alert)
+                    elif isinstance(msg, Certificate):
+                        clientCertificate = msg
+                        if clientCertificate.certChain and \
+                                clientCertificate.certChain.getNumCerts()!=0:
+                            clientCertChain = clientCertificate.certChain
+                    else:
+                        raise AssertionError()
+                elif self.version in ((3,1), (3,2)):
+                    for result in self._getMsg(ContentType.handshake,
+                                              HandshakeType.certificate,
+                                              certificateType):
+                        if result in (0,1):
+                            yield result
+                        else:
+                            break
+                    clientCertificate = result
+                    if clientCertificate.certChain and \
+                            clientCertificate.certChain.getNumCerts()!=0:
+                        clientCertChain = clientCertificate.certChain
+                else:
+                    raise AssertionError()
+
+            #Get ClientKeyExchange
+            for result in self._getMsg(ContentType.handshake,
+                                      HandshakeType.client_key_exchange,
+                                      cipherSuite):
+                if result in (0,1):
+                    yield result
+                else:
+                    break
+            clientKeyExchange = result
+
+            #Decrypt ClientKeyExchange
+            premasterSecret = privateKey.decrypt(\
+                clientKeyExchange.encryptedPreMasterSecret)
+
+            randomPreMasterSecret = getRandomBytes(48)
+            versionCheck = (premasterSecret[0], premasterSecret[1])
+            if not premasterSecret:
+                premasterSecret = randomPreMasterSecret
+            elif len(premasterSecret)!=48:
+                premasterSecret = randomPreMasterSecret
+            elif versionCheck != clientHello.client_version:
+                if versionCheck != self.version: #Tolerate buggy IE clients
+                    premasterSecret = randomPreMasterSecret
+
+            #Get and check CertificateVerify, if relevant
+            if clientCertChain:
+                if self.version == (3,0):
+                    #Create a temporary session object, just for the purpose
+                    #of checking the CertificateVerify
+                    session = Session()
+                    session._calcMasterSecret(self.version, premasterSecret,
+                                             clientRandom, serverRandom)
+                    verifyBytes = self._calcSSLHandshakeHash(\
+                                    session.masterSecret, "")
+                elif self.version in ((3,1), (3,2)):
+                    verifyBytes = stringToBytes(self._handshake_md5.digest() +\
+                                                self._handshake_sha.digest())
+                for result in self._getMsg(ContentType.handshake,
+                                          HandshakeType.certificate_verify):
+                    if result in (0,1):
+                        yield result
+                    else:
+                        break
+                certificateVerify = result
+                publicKey = clientCertChain.getEndEntityPublicKey()
+                if len(publicKey) < settings.minKeySize:
+                    postFinishedError = (AlertDescription.handshake_failure,
+                        "Client's public key too small: %d" % len(publicKey))
+                if len(publicKey) > settings.maxKeySize:
+                    postFinishedError = (AlertDescription.handshake_failure,
+                        "Client's public key too large: %d" % len(publicKey))
+
+                if not publicKey.verify(certificateVerify.signature,
+                                        verifyBytes):
+                    postFinishedError = (AlertDescription.decrypt_error,
+                                         "Signature failed to verify")
+
+
+        #Create the session object
+        self.session = Session()
+        self.session._calcMasterSecret(self.version, premasterSecret,
+                                      clientRandom, serverRandom)
+        self.session.sessionID = sessionID
+        self.session.cipherSuite = cipherSuite
+        self.session.srpUsername = self.allegedSrpUsername
+        self.session.clientCertChain = clientCertChain
+        self.session.serverCertChain = serverCertChain
+
+        #Calculate pending connection states
+        self._calcPendingStates(clientRandom, serverRandom,
+                               settings.cipherImplementations)
+
+        #Exchange ChangeCipherSpec and Finished messages
+        for result in self._getFinished():
+            yield result
+
+        #If we were holding a post-finished error until receiving the client
+        #finished message, send it now.  We delay the call until this point
+        #because calling sendError() throws an exception, and our caller might
+        #shut down the socket upon receiving the exception.  If he did, and the
+        #client was still sending its ChangeCipherSpec or Finished messages, it
+        #would cause a socket error on the client side.  This is a lot of
+        #consideration to show to misbehaving clients, but this would also
+        #cause problems with fault-testing.
+        if postFinishedError:
+            for result in self._sendError(*postFinishedError):
+                yield result
+
+        for result in self._sendFinished():
+            yield result
+
+        #Add the session object to the session cache
+        if sessionCache and sessionID:
+            sessionCache[bytesToString(sessionID)] = self.session
+
+        #Mark the connection as open
+        self.session._setResumable(True)
+        self._handshakeDone(resumed=False)
+
+
+    def _handshakeWrapperAsync(self, handshaker, checker):
+        if not self.fault:
+            try:
+                for result in handshaker:
+                    yield result
+                if checker:
+                    try:
+                        checker(self)
+                    except TLSAuthenticationError:
+                        alert = Alert().create(AlertDescription.close_notify,
+                                               AlertLevel.fatal)
+                        for result in self._sendMsg(alert):
+                            yield result
+                        raise
+            except:
+                    self._shutdown(False)
+                    raise
+        else:
+            try:
+                for result in handshaker:
+                    yield result
+                if checker:
+                    try:
+                        checker(self)
+                    except TLSAuthenticationError:
+                        alert = Alert().create(AlertDescription.close_notify,
+                                               AlertLevel.fatal)
+                        for result in self._sendMsg(alert):
+                            yield result
+                        raise
+            except socket.error, e:
+                raise TLSFaultError("socket error!")
+            except TLSAbruptCloseError, e:
+                raise TLSFaultError("abrupt close error!")
+            except TLSAlert, alert:
+                if alert.description not in Fault.faultAlerts[self.fault]:
+                    raise TLSFaultError(str(alert))
+                else:
+                    pass
+            except:
+                self._shutdown(False)
+                raise
+            else:
+                raise TLSFaultError("No error!")
+
+
+    def _getKeyFromChain(self, certificate, settings):
+        #Get and check cert chain from the Certificate message
+        certChain = certificate.certChain
+        if not certChain or certChain.getNumCerts() == 0:
+            for result in self._sendError(AlertDescription.illegal_parameter,
+                    "Other party sent a Certificate message without "\
+                    "certificates"):
+                yield result
+
+        #Get and check public key from the cert chain
+        publicKey = certChain.getEndEntityPublicKey()
+        if len(publicKey) < settings.minKeySize:
+            for result in self._sendError(AlertDescription.handshake_failure,
+                    "Other party's public key too small: %d" % len(publicKey)):
+                yield result
+        if len(publicKey) > settings.maxKeySize:
+            for result in self._sendError(AlertDescription.handshake_failure,
+                    "Other party's public key too large: %d" % len(publicKey)):
+                yield result
+
+        yield publicKey, certChain

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/TLSRecordLayer.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/TLSRecordLayer.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,1123 @@
+"""Helper class for TLSConnection."""
+from __future__ import generators
+
+from utils.compat import *
+from utils.cryptomath import *
+from utils.cipherfactory import createAES, createRC4, createTripleDES
+from utils.codec import *
+from errors import *
+from messages import *
+from mathtls import *
+from constants import *
+from utils.cryptomath import getRandomBytes
+from utils import hmac
+from FileObject import FileObject
+import sha
+import md5
+import socket
+import errno
+import traceback
+
+class _ConnectionState:
+    def __init__(self):
+        self.macContext = None
+        self.encContext = None
+        self.seqnum = 0
+
+    def getSeqNumStr(self):
+        w = Writer(8)
+        w.add(self.seqnum, 8)
+        seqnumStr = bytesToString(w.bytes)
+        self.seqnum += 1
+        return seqnumStr
+
+
+class TLSRecordLayer:
+    """
+    This class handles data transmission for a TLS connection.
+
+    Its only subclass is L{tlslite.TLSConnection.TLSConnection}.  We've
+    separated the code in this class from TLSConnection to make things
+    more readable.
+
+
+    @type sock: socket.socket
+    @ivar sock: The underlying socket object.
+
+    @type session: L{tlslite.Session.Session}
+    @ivar session: The session corresponding to this connection.
+
+    Due to TLS session resumption, multiple connections can correspond
+    to the same underlying session.
+
+    @type version: tuple
+    @ivar version: The TLS version being used for this connection.
+
+    (3,0) means SSL 3.0, and (3,1) means TLS 1.0.
+
+    @type closed: bool
+    @ivar closed: If this connection is closed.
+
+    @type resumed: bool
+    @ivar resumed: If this connection is based on a resumed session.
+
+    @type allegedSharedKeyUsername: str or None
+    @ivar allegedSharedKeyUsername:  This is set to the shared-key
+    username asserted by the client, whether the handshake succeeded or
+    not.  If the handshake fails, this can be inspected to
+    determine if a guessing attack is in progress against a particular
+    user account.
+
+    @type allegedSrpUsername: str or None
+    @ivar allegedSrpUsername:  This is set to the SRP username
+    asserted by the client, whether the handshake succeeded or not.
+    If the handshake fails, this can be inspected to determine
+    if a guessing attack is in progress against a particular user
+    account.
+
+    @type closeSocket: bool
+    @ivar closeSocket: If the socket should be closed when the
+    connection is closed (writable).
+
+    If you set this to True, TLS Lite will assume the responsibility of
+    closing the socket when the TLS Connection is shutdown (either
+    through an error or through the user calling close()).  The default
+    is False.
+
+    @type ignoreAbruptClose: bool
+    @ivar ignoreAbruptClose: If an abrupt close of the socket should
+    raise an error (writable).
+
+    If you set this to True, TLS Lite will not raise a
+    L{tlslite.errors.TLSAbruptCloseError} exception if the underlying
+    socket is unexpectedly closed.  Such an unexpected closure could be
+    caused by an attacker.  However, it also occurs with some incorrect
+    TLS implementations.
+
+    You should set this to True only if you're not worried about an
+    attacker truncating the connection, and only if necessary to avoid
+    spurious errors.  The default is False.
+
+    @sort: __init__, read, readAsync, write, writeAsync, close, closeAsync,
+    getCipherImplementation, getCipherName
+    """
+
+    def __init__(self, sock):
+        self.sock = sock
+
+        #My session object (Session instance; read-only)
+        self.session = None
+
+        #Am I a client or server?
+        self._client = None
+
+        #Buffers for processing messages
+        self._handshakeBuffer = []
+        self._readBuffer = ""
+
+        #Handshake digests
+        self._handshake_md5 = md5.md5()
+        self._handshake_sha = sha.sha()
+
+        #TLS Protocol Version
+        self.version = (0,0) #read-only
+        self._versionCheck = False #Once we choose a version, this is True
+
+        #Current and Pending connection states
+        self._writeState = _ConnectionState()
+        self._readState = _ConnectionState()
+        self._pendingWriteState = _ConnectionState()
+        self._pendingReadState = _ConnectionState()
+
+        #Is the connection open?
+        self.closed = True #read-only
+        self._refCount = 0 #Used to trigger closure
+
+        #Is this a resumed (or shared-key) session?
+        self.resumed = False #read-only
+
+        #What username did the client claim in his handshake?
+        self.allegedSharedKeyUsername = None
+        self.allegedSrpUsername = None
+
+        #On a call to close(), do we close the socket? (writeable)
+        self.closeSocket = False
+
+        #If the socket is abruptly closed, do we ignore it
+        #and pretend the connection was shut down properly? (writeable)
+        self.ignoreAbruptClose = False
+
+        #Fault we will induce, for testing purposes
+        self.fault = None
+
+    #*********************************************************
+    # Public Functions START
+    #*********************************************************
+
+    def read(self, max=None, min=1):
+        """Read some data from the TLS connection.
+
+        This function will block until at least 'min' bytes are
+        available (or the connection is closed).
+
+        If an exception is raised, the connection will have been
+        automatically closed.
+
+        @type max: int
+        @param max: The maximum number of bytes to return.
+
+        @type min: int
+        @param min: The minimum number of bytes to return
+
+        @rtype: str
+        @return: A string of no more than 'max' bytes, and no fewer
+        than 'min' (unless the connection has been closed, in which
+        case fewer than 'min' bytes may be returned).
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        """
+        for result in self.readAsync(max, min):
+            pass
+        return result
+
+    def readAsync(self, max=None, min=1):
+        """Start a read operation on the TLS connection.
+
+        This function returns a generator which behaves similarly to
+        read().  Successive invocations of the generator will return 0
+        if it is waiting to read from the socket, 1 if it is waiting
+        to write to the socket, or a string if the read operation has
+        completed.
+
+        @rtype: iterable
+        @return: A generator; see above for details.
+        """
+        try:
+            while len(self._readBuffer)<min and not self.closed:
+                try:
+                    for result in self._getMsg(ContentType.application_data):
+                        if result in (0,1):
+                            yield result
+                    applicationData = result
+                    self._readBuffer += bytesToString(applicationData.write())
+                except TLSRemoteAlert, alert:
+                    if alert.description != AlertDescription.close_notify:
+                        raise
+                except TLSAbruptCloseError:
+                    if not self.ignoreAbruptClose:
+                        raise
+                    else:
+                        self._shutdown(True)
+
+            if max == None:
+                max = len(self._readBuffer)
+
+            returnStr = self._readBuffer[:max]
+            self._readBuffer = self._readBuffer[max:]
+            yield returnStr
+        except:
+            self._shutdown(False)
+            raise
+
+    def write(self, s):
+        """Write some data to the TLS connection.
+
+        This function will block until all the data has been sent.
+
+        If an exception is raised, the connection will have been
+        automatically closed.
+
+        @type s: str
+        @param s: The data to transmit to the other party.
+
+        @raise socket.error: If a socket error occurs.
+        """
+        for result in self.writeAsync(s):
+            pass
+
+    def writeAsync(self, s):
+        """Start a write operation on the TLS connection.
+
+        This function returns a generator which behaves similarly to
+        write().  Successive invocations of the generator will return
+        1 if it is waiting to write to the socket, or will raise
+        StopIteration if the write operation has completed.
+
+        @rtype: iterable
+        @return: A generator; see above for details.
+        """
+        try:
+            if self.closed:
+                raise ValueError()
+
+            index = 0
+            blockSize = 16384
+            skipEmptyFrag = False
+            while 1:
+                startIndex = index * blockSize
+                endIndex = startIndex + blockSize
+                if startIndex >= len(s):
+                    break
+                if endIndex > len(s):
+                    endIndex = len(s)
+                block = stringToBytes(s[startIndex : endIndex])
+                applicationData = ApplicationData().create(block)
+                for result in self._sendMsg(applicationData, skipEmptyFrag):
+                    yield result
+                skipEmptyFrag = True #only send an empy fragment on 1st message
+                index += 1
+        except:
+            self._shutdown(False)
+            raise
+
+    def close(self):
+        """Close the TLS connection.
+
+        This function will block until it has exchanged close_notify
+        alerts with the other party.  After doing so, it will shut down the
+        TLS connection.  Further attempts to read through this connection
+        will return "".  Further attempts to write through this connection
+        will raise ValueError.
+
+        If makefile() has been called on this connection, the connection
+        will be not be closed until the connection object and all file
+        objects have been closed.
+
+        Even if an exception is raised, the connection will have been
+        closed.
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        """
+        if not self.closed:
+            for result in self._decrefAsync():
+                pass
+
+    def closeAsync(self):
+        """Start a close operation on the TLS connection.
+
+        This function returns a generator which behaves similarly to
+        close().  Successive invocations of the generator will return 0
+        if it is waiting to read from the socket, 1 if it is waiting
+        to write to the socket, or will raise StopIteration if the
+        close operation has completed.
+
+        @rtype: iterable
+        @return: A generator; see above for details.
+        """
+        if not self.closed:
+            for result in self._decrefAsync():
+                yield result
+
+    def _decrefAsync(self):
+        self._refCount -= 1
+        if self._refCount == 0 and not self.closed:
+            try:
+                for result in self._sendMsg(Alert().create(\
+                        AlertDescription.close_notify, AlertLevel.warning)):
+                    yield result
+                alert = None
+                while not alert:
+                    for result in self._getMsg((ContentType.alert, \
+                                              ContentType.application_data)):
+                        if result in (0,1):
+                            yield result
+                    if result.contentType == ContentType.alert:
+                        alert = result
+                if alert.description == AlertDescription.close_notify:
+                    self._shutdown(True)
+                else:
+                    raise TLSRemoteAlert(alert)
+            except (socket.error, TLSAbruptCloseError):
+                #If the other side closes the socket, that's okay
+                self._shutdown(True)
+            except:
+                self._shutdown(False)
+                raise
+
+    def getCipherName(self):
+        """Get the name of the cipher used with this connection.
+
+        @rtype: str
+        @return: The name of the cipher used with this connection.
+        Either 'aes128', 'aes256', 'rc4', or '3des'.
+        """
+        if not self._writeState.encContext:
+            return None
+        return self._writeState.encContext.name
+
+    def getCipherImplementation(self):
+        """Get the name of the cipher implementation used with
+        this connection.
+
+        @rtype: str
+        @return: The name of the cipher implementation used with
+        this connection.  Either 'python', 'cryptlib', 'openssl',
+        or 'pycrypto'.
+        """
+        if not self._writeState.encContext:
+            return None
+        return self._writeState.encContext.implementation
+
+
+
+    #Emulate a socket, somewhat -
+    def send(self, s):
+        """Send data to the TLS connection (socket emulation).
+
+        @raise socket.error: If a socket error occurs.
+        """
+        self.write(s)
+        return len(s)
+
+    def sendall(self, s):
+        """Send data to the TLS connection (socket emulation).
+
+        @raise socket.error: If a socket error occurs.
+        """
+        self.write(s)
+
+    def recv(self, bufsize):
+        """Get some data from the TLS connection (socket emulation).
+
+        @raise socket.error: If a socket error occurs.
+        @raise tlslite.errors.TLSAbruptCloseError: If the socket is closed
+        without a preceding alert.
+        @raise tlslite.errors.TLSAlert: If a TLS alert is signalled.
+        """
+        return self.read(bufsize)
+
+    def makefile(self, mode='r', bufsize=-1):
+        """Create a file object for the TLS connection (socket emulation).
+
+        @rtype: L{tlslite.FileObject.FileObject}
+        """
+        self._refCount += 1
+        return FileObject(self, mode, bufsize)
+
+    def getsockname(self):
+        """Return the socket's own address (socket emulation)."""
+        return self.sock.getsockname()
+
+    def getpeername(self):
+        """Return the remote address to which the socket is connected
+        (socket emulation)."""
+        return self.sock.getpeername()
+
+    def settimeout(self, value):
+        """Set a timeout on blocking socket operations (socket emulation)."""
+        return self.sock.settimeout(value)
+
+    def gettimeout(self):
+        """Return the timeout associated with socket operations (socket
+        emulation)."""
+        return self.sock.gettimeout()
+
+    def setsockopt(self, level, optname, value):
+        """Set the value of the given socket option (socket emulation)."""
+        return self.sock.setsockopt(level, optname, value)
+
+
+     #*********************************************************
+     # Public Functions END
+     #*********************************************************
+
+    def _shutdown(self, resumable):
+        self._writeState = _ConnectionState()
+        self._readState = _ConnectionState()
+        #Don't do this: self._readBuffer = ""
+        self.version = (0,0)
+        self._versionCheck = False
+        self.closed = True
+        if self.closeSocket:
+            self.sock.close()
+
+        #Even if resumable is False, we'll never toggle this on
+        if not resumable and self.session:
+            self.session.resumable = False
+
+
+    def _sendError(self, alertDescription, errorStr=None):
+        alert = Alert().create(alertDescription, AlertLevel.fatal)
+        for result in self._sendMsg(alert):
+            yield result
+        self._shutdown(False)
+        raise TLSLocalAlert(alert, errorStr)
+
+    def _sendMsgs(self, msgs):
+        skipEmptyFrag = False
+        for msg in msgs:
+            for result in self._sendMsg(msg, skipEmptyFrag):
+                yield result
+            skipEmptyFrag = True
+
+    def _sendMsg(self, msg, skipEmptyFrag=False):
+        bytes = msg.write()
+        contentType = msg.contentType
+
+        #Whenever we're connected and asked to send a message,
+        #we first send an empty Application Data message.  This prevents
+        #an attacker from launching a chosen-plaintext attack based on
+        #knowing the next IV.
+        if not self.closed and not skipEmptyFrag and self.version == (3,1):
+            if self._writeState.encContext:
+                if self._writeState.encContext.isBlockCipher:
+                    for result in self._sendMsg(ApplicationData(),
+                                               skipEmptyFrag=True):
+                        yield result
+
+        #Update handshake hashes
+        if contentType == ContentType.handshake:
+            bytesStr = bytesToString(bytes)
+            self._handshake_md5.update(bytesStr)
+            self._handshake_sha.update(bytesStr)
+
+        #Calculate MAC
+        if self._writeState.macContext:
+            seqnumStr = self._writeState.getSeqNumStr()
+            bytesStr = bytesToString(bytes)
+            mac = self._writeState.macContext.copy()
+            mac.update(seqnumStr)
+            mac.update(chr(contentType))
+            if self.version == (3,0):
+                mac.update( chr( int(len(bytes)/256) ) )
+                mac.update( chr( int(len(bytes)%256) ) )
+            elif self.version in ((3,1), (3,2)):
+                mac.update(chr(self.version[0]))
+                mac.update(chr(self.version[1]))
+                mac.update( chr( int(len(bytes)/256) ) )
+                mac.update( chr( int(len(bytes)%256) ) )
+            else:
+                raise AssertionError()
+            mac.update(bytesStr)
+            macString = mac.digest()
+            macBytes = stringToBytes(macString)
+            if self.fault == Fault.badMAC:
+                macBytes[0] = (macBytes[0]+1) % 256
+
+        #Encrypt for Block or Stream Cipher
+        if self._writeState.encContext:
+            #Add padding and encrypt (for Block Cipher):
+            if self._writeState.encContext.isBlockCipher:
+
+                #Add TLS 1.1 fixed block
+                if self.version == (3,2):
+                    bytes = self.fixedIVBlock + bytes
+
+                #Add padding: bytes = bytes + (macBytes + paddingBytes)
+                currentLength = len(bytes) + len(macBytes) + 1
+                blockLength = self._writeState.encContext.block_size
+                paddingLength = blockLength-(currentLength % blockLength)
+
+                paddingBytes = createByteArraySequence([paddingLength] * \
+                                                       (paddingLength+1))
+                if self.fault == Fault.badPadding:
+                    paddingBytes[0] = (paddingBytes[0]+1) % 256
+                endBytes = concatArrays(macBytes, paddingBytes)
+                bytes = concatArrays(bytes, endBytes)
+                #Encrypt
+                plaintext = stringToBytes(bytes)
+                ciphertext = self._writeState.encContext.encrypt(plaintext)
+                bytes = stringToBytes(ciphertext)
+
+            #Encrypt (for Stream Cipher)
+            else:
+                bytes = concatArrays(bytes, macBytes)
+                plaintext = bytesToString(bytes)
+                ciphertext = self._writeState.encContext.encrypt(plaintext)
+                bytes = stringToBytes(ciphertext)
+
+        #Add record header and send
+        r = RecordHeader3().create(self.version, contentType, len(bytes))
+        s = bytesToString(concatArrays(r.write(), bytes))
+        while 1:
+            try:
+                bytesSent = self.sock.send(s) #Might raise socket.error
+            except socket.error, why:
+                if why[0] == errno.EWOULDBLOCK:
+                    yield 1
+                    continue
+                else:
+                    raise
+            if bytesSent == len(s):
+                return
+            s = s[bytesSent:]
+            yield 1
+
+
+    def _getMsg(self, expectedType, secondaryType=None, constructorType=None):
+        try:
+            if not isinstance(expectedType, tuple):
+                expectedType = (expectedType,)
+
+            #Spin in a loop, until we've got a non-empty record of a type we
+            #expect.  The loop will be repeated if:
+            #  - we receive a renegotiation attempt; we send no_renegotiation,
+            #    then try again
+            #  - we receive an empty application-data fragment; we try again
+            while 1:
+                for result in self._getNextRecord():
+                    if result in (0,1):
+                        yield result
+                recordHeader, p = result
+
+                #If this is an empty application-data fragment, try again
+                if recordHeader.type == ContentType.application_data:
+                    if p.index == len(p.bytes):
+                        continue
+
+                #If we received an unexpected record type...
+                if recordHeader.type not in expectedType:
+
+                    #If we received an alert...
+                    if recordHeader.type == ContentType.alert:
+                        alert = Alert().parse(p)
+
+                        #We either received a fatal error, a warning, or a
+                        #close_notify.  In any case, we're going to close the
+                        #connection.  In the latter two cases we respond with
+                        #a close_notify, but ignore any socket errors, since
+                        #the other side might have already closed the socket.
+                        if alert.level == AlertLevel.warning or \
+                           alert.description == AlertDescription.close_notify:
+
+                            #If the sendMsg() call fails because the socket has
+                            #already been closed, we will be forgiving and not
+                            #report the error nor invalidate the "resumability"
+                            #of the session.
+                            try:
+                                alertMsg = Alert()
+                                alertMsg.create(AlertDescription.close_notify,
+                                                AlertLevel.warning)
+                                for result in self._sendMsg(alertMsg):
+                                    yield result
+                            except socket.error:
+                                pass
+
+                            if alert.description == \
+                                   AlertDescription.close_notify:
+                                self._shutdown(True)
+                            elif alert.level == AlertLevel.warning:
+                                self._shutdown(False)
+
+                        else: #Fatal alert:
+                            self._shutdown(False)
+
+                        #Raise the alert as an exception
+                        raise TLSRemoteAlert(alert)
+
+                    #If we received a renegotiation attempt...
+                    if recordHeader.type == ContentType.handshake:
+                        subType = p.get(1)
+                        reneg = False
+                        if self._client:
+                            if subType == HandshakeType.hello_request:
+                                reneg = True
+                        else:
+                            if subType == HandshakeType.client_hello:
+                                reneg = True
+                        #Send no_renegotiation, then try again
+                        if reneg:
+                            alertMsg = Alert()
+                            alertMsg.create(AlertDescription.no_renegotiation,
+                                            AlertLevel.warning)
+                            for result in self._sendMsg(alertMsg):
+                                yield result
+                            continue
+
+                    #Otherwise: this is an unexpected record, but neither an
+                    #alert nor renegotiation
+                    for result in self._sendError(\
+                            AlertDescription.unexpected_message,
+                            "received type=%d" % recordHeader.type):
+                        yield result
+
+                break
+
+            #Parse based on content_type
+            if recordHeader.type == ContentType.change_cipher_spec:
+                yield ChangeCipherSpec().parse(p)
+            elif recordHeader.type == ContentType.alert:
+                yield Alert().parse(p)
+            elif recordHeader.type == ContentType.application_data:
+                yield ApplicationData().parse(p)
+            elif recordHeader.type == ContentType.handshake:
+                #Convert secondaryType to tuple, if it isn't already
+                if not isinstance(secondaryType, tuple):
+                    secondaryType = (secondaryType,)
+
+                #If it's a handshake message, check handshake header
+                if recordHeader.ssl2:
+                    subType = p.get(1)
+                    if subType != HandshakeType.client_hello:
+                        for result in self._sendError(\
+                                AlertDescription.unexpected_message,
+                                "Can only handle SSLv2 ClientHello messages"):
+                            yield result
+                    if HandshakeType.client_hello not in secondaryType:
+                        for result in self._sendError(\
+                                AlertDescription.unexpected_message):
+                            yield result
+                    subType = HandshakeType.client_hello
+                else:
+                    subType = p.get(1)
+                    if subType not in secondaryType:
+                        for result in self._sendError(\
+                                AlertDescription.unexpected_message,
+                                "Expecting %s, got %s" % (str(secondaryType), subType)):
+                            yield result
+
+                #Update handshake hashes
+                sToHash = bytesToString(p.bytes)
+                self._handshake_md5.update(sToHash)
+                self._handshake_sha.update(sToHash)
+
+                #Parse based on handshake type
+                if subType == HandshakeType.client_hello:
+                    yield ClientHello(recordHeader.ssl2).parse(p)
+                elif subType == HandshakeType.server_hello:
+                    yield ServerHello().parse(p)
+                elif subType == HandshakeType.certificate:
+                    yield Certificate(constructorType).parse(p)
+                elif subType == HandshakeType.certificate_request:
+                    yield CertificateRequest().parse(p)
+                elif subType == HandshakeType.certificate_verify:
+                    yield CertificateVerify().parse(p)
+                elif subType == HandshakeType.server_key_exchange:
+                    yield ServerKeyExchange(constructorType).parse(p)
+                elif subType == HandshakeType.server_hello_done:
+                    yield ServerHelloDone().parse(p)
+                elif subType == HandshakeType.client_key_exchange:
+                    yield ClientKeyExchange(constructorType, \
+                                            self.version).parse(p)
+                elif subType == HandshakeType.finished:
+                    yield Finished(self.version).parse(p)
+                else:
+                    raise AssertionError()
+
+        #If an exception was raised by a Parser or Message instance:
+        except SyntaxError, e:
+            for result in self._sendError(AlertDescription.decode_error,
+                                         formatExceptionTrace(e)):
+                yield result
+
+
+    #Returns next record or next handshake message
+    def _getNextRecord(self):
+
+        #If there's a handshake message waiting, return it
+        if self._handshakeBuffer:
+            recordHeader, bytes = self._handshakeBuffer[0]
+            self._handshakeBuffer = self._handshakeBuffer[1:]
+            yield (recordHeader, Parser(bytes))
+            return
+
+        #Otherwise...
+        #Read the next record header
+        bytes = createByteArraySequence([])
+        recordHeaderLength = 1
+        ssl2 = False
+        while 1:
+            try:
+                s = self.sock.recv(recordHeaderLength-len(bytes))
+            except socket.error, why:
+                if why[0] == errno.EWOULDBLOCK:
+                    yield 0
+                    continue
+                else:
+                    raise
+
+            #If the connection was abruptly closed, raise an error
+            if len(s)==0:
+                raise TLSAbruptCloseError()
+
+            bytes += stringToBytes(s)
+            if len(bytes)==1:
+                if bytes[0] in ContentType.all:
+                    ssl2 = False
+                    recordHeaderLength = 5
+                elif bytes[0] == 128:
+                    ssl2 = True
+                    recordHeaderLength = 2
+                else:
+                    raise SyntaxError()
+            if len(bytes) == recordHeaderLength:
+                break
+
+        #Parse the record header
+        if ssl2:
+            r = RecordHeader2().parse(Parser(bytes))
+        else:
+            r = RecordHeader3().parse(Parser(bytes))
+
+        #Check the record header fields
+        if r.length > 18432:
+            for result in self._sendError(AlertDescription.record_overflow):
+                yield result
+
+        #Read the record contents
+        bytes = createByteArraySequence([])
+        while 1:
+            try:
+                s = self.sock.recv(r.length - len(bytes))
+            except socket.error, why:
+                if why[0] == errno.EWOULDBLOCK:
+                    yield 0
+                    continue
+                else:
+                    raise
+
+            #If the connection is closed, raise a socket error
+            if len(s)==0:
+                    raise TLSAbruptCloseError()
+
+            bytes += stringToBytes(s)
+            if len(bytes) == r.length:
+                break
+
+        #Check the record header fields (2)
+        #We do this after reading the contents from the socket, so that
+        #if there's an error, we at least don't leave extra bytes in the
+        #socket..
+        #
+        # THIS CHECK HAS NO SECURITY RELEVANCE (?), BUT COULD HURT INTEROP.
+        # SO WE LEAVE IT OUT FOR NOW.
+        #
+        #if self._versionCheck and r.version != self.version:
+        #    for result in self._sendError(AlertDescription.protocol_version,
+        #            "Version in header field: %s, should be %s" % (str(r.version),
+        #                                                       str(self.version))):
+        #        yield result
+
+        #Decrypt the record
+        for result in self._decryptRecord(r.type, bytes):
+            if result in (0,1):
+                yield result
+            else:
+                break
+        bytes = result
+        p = Parser(bytes)
+
+        #If it doesn't contain handshake messages, we can just return it
+        if r.type != ContentType.handshake:
+            yield (r, p)
+        #If it's an SSLv2 ClientHello, we can return it as well
+        elif r.ssl2:
+            yield (r, p)
+        else:
+            #Otherwise, we loop through and add the handshake messages to the
+            #handshake buffer
+            while 1:
+                if p.index == len(bytes): #If we're at the end
+                    if not self._handshakeBuffer:
+                        for result in self._sendError(\
+                                AlertDescription.decode_error, \
+                                "Received empty handshake record"):
+                            yield result
+                    break
+                #There needs to be at least 4 bytes to get a header
+                if p.index+4 > len(bytes):
+                    for result in self._sendError(\
+                            AlertDescription.decode_error,
+                            "A record has a partial handshake message (1)"):
+                        yield result
+                p.get(1) # skip handshake type
+                msgLength = p.get(3)
+                if p.index+msgLength > len(bytes):
+                    for result in self._sendError(\
+                            AlertDescription.decode_error,
+                            "A record has a partial handshake message (2)"):
+                        yield result
+
+                handshakePair = (r, bytes[p.index-4 : p.index+msgLength])
+                self._handshakeBuffer.append(handshakePair)
+                p.index += msgLength
+
+            #We've moved at least one handshake message into the
+            #handshakeBuffer, return the first one
+            recordHeader, bytes = self._handshakeBuffer[0]
+            self._handshakeBuffer = self._handshakeBuffer[1:]
+            yield (recordHeader, Parser(bytes))
+
+
+    def _decryptRecord(self, recordType, bytes):
+        if self._readState.encContext:
+
+            #Decrypt if it's a block cipher
+            if self._readState.encContext.isBlockCipher:
+                blockLength = self._readState.encContext.block_size
+                if len(bytes) % blockLength != 0:
+                    for result in self._sendError(\
+                            AlertDescription.decryption_failed,
+                            "Encrypted data not a multiple of blocksize"):
+                        yield result
+                ciphertext = bytesToString(bytes)
+                plaintext = self._readState.encContext.decrypt(ciphertext)
+                if self.version == (3,2): #For TLS 1.1, remove explicit IV
+                    plaintext = plaintext[self._readState.encContext.block_size : ]
+                bytes = stringToBytes(plaintext)
+
+                #Check padding
+                paddingGood = True
+                paddingLength = bytes[-1]
+                if (paddingLength+1) > len(bytes):
+                    paddingGood=False
+                    totalPaddingLength = 0
+                else:
+                    if self.version == (3,0):
+                        totalPaddingLength = paddingLength+1
+                    elif self.version in ((3,1), (3,2)):
+                        totalPaddingLength = paddingLength+1
+                        paddingBytes = bytes[-totalPaddingLength:-1]
+                        for byte in paddingBytes:
+                            if byte != paddingLength:
+                                paddingGood = False
+                                totalPaddingLength = 0
+                    else:
+                        raise AssertionError()
+
+            #Decrypt if it's a stream cipher
+            else:
+                paddingGood = True
+                ciphertext = bytesToString(bytes)
+                plaintext = self._readState.encContext.decrypt(ciphertext)
+                bytes = stringToBytes(plaintext)
+                totalPaddingLength = 0
+
+            #Check MAC
+            macGood = True
+            macLength = self._readState.macContext.digest_size
+            endLength = macLength + totalPaddingLength
+            if endLength > len(bytes):
+                macGood = False
+            else:
+                #Read MAC
+                startIndex = len(bytes) - endLength
+                endIndex = startIndex + macLength
+                checkBytes = bytes[startIndex : endIndex]
+
+                #Calculate MAC
+                seqnumStr = self._readState.getSeqNumStr()
+                bytes = bytes[:-endLength]
+                bytesStr = bytesToString(bytes)
+                mac = self._readState.macContext.copy()
+                mac.update(seqnumStr)
+                mac.update(chr(recordType))
+                if self.version == (3,0):
+                    mac.update( chr( int(len(bytes)/256) ) )
+                    mac.update( chr( int(len(bytes)%256) ) )
+                elif self.version in ((3,1), (3,2)):
+                    mac.update(chr(self.version[0]))
+                    mac.update(chr(self.version[1]))
+                    mac.update( chr( int(len(bytes)/256) ) )
+                    mac.update( chr( int(len(bytes)%256) ) )
+                else:
+                    raise AssertionError()
+                mac.update(bytesStr)
+                macString = mac.digest()
+                macBytes = stringToBytes(macString)
+
+                #Compare MACs
+                if macBytes != checkBytes:
+                    macGood = False
+
+            if not (paddingGood and macGood):
+                for result in self._sendError(AlertDescription.bad_record_mac,
+                                          "MAC failure (or padding failure)"):
+                    yield result
+
+        yield bytes
+
+    def _handshakeStart(self, client):
+        self._client = client
+        self._handshake_md5 = md5.md5()
+        self._handshake_sha = sha.sha()
+        self._handshakeBuffer = []
+        self.allegedSharedKeyUsername = None
+        self.allegedSrpUsername = None
+        self._refCount = 1
+
+    def _handshakeDone(self, resumed):
+        self.resumed = resumed
+        self.closed = False
+
+    def _calcPendingStates(self, clientRandom, serverRandom, implementations):
+        if self.session.cipherSuite in CipherSuite.aes128Suites:
+            macLength = 20
+            keyLength = 16
+            ivLength = 16
+            createCipherFunc = createAES
+        elif self.session.cipherSuite in CipherSuite.aes256Suites:
+            macLength = 20
+            keyLength = 32
+            ivLength = 16
+            createCipherFunc = createAES
+        elif self.session.cipherSuite in CipherSuite.rc4Suites:
+            macLength = 20
+            keyLength = 16
+            ivLength = 0
+            createCipherFunc = createRC4
+        elif self.session.cipherSuite in CipherSuite.tripleDESSuites:
+            macLength = 20
+            keyLength = 24
+            ivLength = 8
+            createCipherFunc = createTripleDES
+        else:
+            raise AssertionError()
+
+        if self.version == (3,0):
+            createMACFunc = MAC_SSL
+        elif self.version in ((3,1), (3,2)):
+            createMACFunc = hmac.HMAC
+
+        outputLength = (macLength*2) + (keyLength*2) + (ivLength*2)
+
+        #Calculate Keying Material from Master Secret
+        if self.version == (3,0):
+            keyBlock = PRF_SSL(self.session.masterSecret,
+                               concatArrays(serverRandom, clientRandom),
+                               outputLength)
+        elif self.version in ((3,1), (3,2)):
+            keyBlock = PRF(self.session.masterSecret,
+                           "key expansion",
+                           concatArrays(serverRandom,clientRandom),
+                           outputLength)
+        else:
+            raise AssertionError()
+
+        #Slice up Keying Material
+        clientPendingState = _ConnectionState()
+        serverPendingState = _ConnectionState()
+        p = Parser(keyBlock)
+        clientMACBlock = bytesToString(p.getFixBytes(macLength))
+        serverMACBlock = bytesToString(p.getFixBytes(macLength))
+        clientKeyBlock = bytesToString(p.getFixBytes(keyLength))
+        serverKeyBlock = bytesToString(p.getFixBytes(keyLength))
+        clientIVBlock  = bytesToString(p.getFixBytes(ivLength))
+        serverIVBlock  = bytesToString(p.getFixBytes(ivLength))
+        clientPendingState.macContext = createMACFunc(clientMACBlock,
+                                                      digestmod=sha)
+        serverPendingState.macContext = createMACFunc(serverMACBlock,
+                                                      digestmod=sha)
+        clientPendingState.encContext = createCipherFunc(clientKeyBlock,
+                                                         clientIVBlock,
+                                                         implementations)
+        serverPendingState.encContext = createCipherFunc(serverKeyBlock,
+                                                         serverIVBlock,
+                                                         implementations)
+
+        #Assign new connection states to pending states
+        if self._client:
+            self._pendingWriteState = clientPendingState
+            self._pendingReadState = serverPendingState
+        else:
+            self._pendingWriteState = serverPendingState
+            self._pendingReadState = clientPendingState
+
+        if self.version == (3,2) and ivLength:
+            #Choose fixedIVBlock for TLS 1.1 (this is encrypted with the CBC
+            #residue to create the IV for each sent block)
+            self.fixedIVBlock = getRandomBytes(ivLength)
+
+    def _changeWriteState(self):
+        self._writeState = self._pendingWriteState
+        self._pendingWriteState = _ConnectionState()
+
+    def _changeReadState(self):
+        self._readState = self._pendingReadState
+        self._pendingReadState = _ConnectionState()
+
+    def _sendFinished(self):
+        #Send ChangeCipherSpec
+        for result in self._sendMsg(ChangeCipherSpec()):
+            yield result
+
+        #Switch to pending write state
+        self._changeWriteState()
+
+        #Calculate verification data
+        verifyData = self._calcFinished(True)
+        if self.fault == Fault.badFinished:
+            verifyData[0] = (verifyData[0]+1)%256
+
+        #Send Finished message under new state
+        finished = Finished(self.version).create(verifyData)
+        for result in self._sendMsg(finished):
+            yield result
+
+    def _getFinished(self):
+        #Get and check ChangeCipherSpec
+        for result in self._getMsg(ContentType.change_cipher_spec):
+            if result in (0,1):
+                yield result
+        changeCipherSpec = result
+
+        if changeCipherSpec.type != 1:
+            for result in self._sendError(AlertDescription.illegal_parameter,
+                                         "ChangeCipherSpec type incorrect"):
+                yield result
+
+        #Switch to pending read state
+        self._changeReadState()
+
+        #Calculate verification data
+        verifyData = self._calcFinished(False)
+
+        #Get and check Finished message under new state
+        for result in self._getMsg(ContentType.handshake,
+                                  HandshakeType.finished):
+            if result in (0,1):
+                yield result
+        finished = result
+        if finished.verify_data != verifyData:
+            for result in self._sendError(AlertDescription.decrypt_error,
+                                         "Finished message is incorrect"):
+                yield result
+
+    def _calcFinished(self, send=True):
+        if self.version == (3,0):
+            if (self._client and send) or (not self._client and not send):
+                senderStr = "\x43\x4C\x4E\x54"
+            else:
+                senderStr = "\x53\x52\x56\x52"
+
+            verifyData = self._calcSSLHandshakeHash(self.session.masterSecret,
+                                                   senderStr)
+            return verifyData
+
+        elif self.version in ((3,1), (3,2)):
+            if (self._client and send) or (not self._client and not send):
+                label = "client finished"
+            else:
+                label = "server finished"
+
+            handshakeHashes = stringToBytes(self._handshake_md5.digest() + \
+                                            self._handshake_sha.digest())
+            verifyData = PRF(self.session.masterSecret, label, handshakeHashes,
+                             12)
+            return verifyData
+        else:
+            raise AssertionError()
+
+    #Used for Finished messages and CertificateVerify messages in SSL v3
+    def _calcSSLHandshakeHash(self, masterSecret, label):
+        masterSecretStr = bytesToString(masterSecret)
+
+        imac_md5 = self._handshake_md5.copy()
+        imac_sha = self._handshake_sha.copy()
+
+        imac_md5.update(label + masterSecretStr + '\x36'*48)
+        imac_sha.update(label + masterSecretStr + '\x36'*40)
+
+        md5Str = md5.md5(masterSecretStr + ('\x5c'*48) + \
+                         imac_md5.digest()).digest()
+        shaStr = sha.sha(masterSecretStr + ('\x5c'*40) + \
+                         imac_sha.digest()).digest()
+
+        return stringToBytes(md5Str + shaStr)
+

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/VerifierDB.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/VerifierDB.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,90 @@
+"""Class for storing SRP password verifiers."""
+
+from utils.cryptomath import *
+from utils.compat import *
+import mathtls
+from BaseDB import BaseDB
+
+class VerifierDB(BaseDB):
+    """This class represent an in-memory or on-disk database of SRP
+    password verifiers.
+
+    A VerifierDB can be passed to a server handshake to authenticate
+    a client based on one of the verifiers.
+
+    This class is thread-safe.
+    """
+    def __init__(self, filename=None):
+        """Create a new VerifierDB instance.
+
+        @type filename: str
+        @param filename: Filename for an on-disk database, or None for
+        an in-memory database.  If the filename already exists, follow
+        this with a call to open().  To create a new on-disk database,
+        follow this with a call to create().
+        """
+        BaseDB.__init__(self, filename, "verifier")
+
+    def _getItem(self, username, valueStr):
+        (N, g, salt, verifier) = valueStr.split(" ")
+        N = base64ToNumber(N)
+        g = base64ToNumber(g)
+        salt = base64ToString(salt)
+        verifier = base64ToNumber(verifier)
+        return (N, g, salt, verifier)
+
+    def __setitem__(self, username, verifierEntry):
+        """Add a verifier entry to the database.
+
+        @type username: str
+        @param username: The username to associate the verifier with.
+        Must be less than 256 characters in length.  Must not already
+        be in the database.
+
+        @type verifierEntry: tuple
+        @param verifierEntry: The verifier entry to add.  Use
+        L{tlslite.VerifierDB.VerifierDB.makeVerifier} to create a
+        verifier entry.
+        """
+        BaseDB.__setitem__(self, username, verifierEntry)
+
+
+    def _setItem(self, username, value):
+        if len(username)>=256:
+            raise ValueError("username too long")
+        N, g, salt, verifier = value
+        N = numberToBase64(N)
+        g = numberToBase64(g)
+        salt = stringToBase64(salt)
+        verifier = numberToBase64(verifier)
+        valueStr = " ".join( (N, g, salt, verifier)  )
+        return valueStr
+
+    def _checkItem(self, value, username, param):
+        (N, g, salt, verifier) = value
+        x = mathtls.makeX(salt, username, param)
+        v = powMod(g, x, N)
+        return (verifier == v)
+
+
+    def makeVerifier(username, password, bits):
+        """Create a verifier entry which can be stored in a VerifierDB.
+
+        @type username: str
+        @param username: The username for this verifier.  Must be less
+        than 256 characters in length.
+
+        @type password: str
+        @param password: The password for this verifier.
+
+        @type bits: int
+        @param bits: This values specifies which SRP group parameters
+        to use.  It must be one of (1024, 1536, 2048, 3072, 4096, 6144,
+        8192).  Larger values are more secure but slower.  2048 is a
+        good compromise between safety and speed.
+
+        @rtype: tuple
+        @return: A tuple which may be stored in a VerifierDB.
+        """
+        return mathtls.makeVerifier(username, password, bits)
+    makeVerifier = staticmethod(makeVerifier)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/X509.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/X509.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,133 @@
+"""Class representing an X.509 certificate."""
+
+from utils.ASN1Parser import ASN1Parser
+from utils.cryptomath import *
+from utils.keyfactory import _createPublicRSAKey
+
+
+class X509:
+    """This class represents an X.509 certificate.
+
+    @type bytes: L{array.array} of unsigned bytes
+    @ivar bytes: The DER-encoded ASN.1 certificate
+
+    @type publicKey: L{tlslite.utils.RSAKey.RSAKey}
+    @ivar publicKey: The subject public key from the certificate.
+    """
+
+    def __init__(self):
+        self.bytes = createByteArraySequence([])
+        self.publicKey = None
+
+    def parse(self, s):
+        """Parse a PEM-encoded X.509 certificate.
+
+        @type s: str
+        @param s: A PEM-encoded X.509 certificate (i.e. a base64-encoded
+        certificate wrapped with "-----BEGIN CERTIFICATE-----" and
+        "-----END CERTIFICATE-----" tags).
+        """
+
+        start = s.find("-----BEGIN CERTIFICATE-----")
+        end = s.find("-----END CERTIFICATE-----")
+        if start == -1:
+            raise SyntaxError("Missing PEM prefix")
+        if end == -1:
+            raise SyntaxError("Missing PEM postfix")
+        s = s[start+len("-----BEGIN CERTIFICATE-----") : end]
+
+        bytes = base64ToBytes(s)
+        self.parseBinary(bytes)
+        return self
+
+    def parseBinary(self, bytes):
+        """Parse a DER-encoded X.509 certificate.
+
+        @type bytes: str or L{array.array} of unsigned bytes
+        @param bytes: A DER-encoded X.509 certificate.
+        """
+
+        if isinstance(bytes, type("")):
+            bytes = stringToBytes(bytes)
+
+        self.bytes = bytes
+        p = ASN1Parser(bytes)
+
+        #Get the tbsCertificate
+        tbsCertificateP = p.getChild(0)
+
+        #Is the optional version field present?
+        #This determines which index the key is at.
+        if tbsCertificateP.value[0]==0xA0:
+            subjectPublicKeyInfoIndex = 6
+        else:
+            subjectPublicKeyInfoIndex = 5
+
+        #Get the subjectPublicKeyInfo
+        subjectPublicKeyInfoP = tbsCertificateP.getChild(\
+                                    subjectPublicKeyInfoIndex)
+
+        #Get the algorithm
+        algorithmP = subjectPublicKeyInfoP.getChild(0)
+        rsaOID = algorithmP.value
+        if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]:
+            raise SyntaxError("Unrecognized AlgorithmIdentifier")
+
+        #Get the subjectPublicKey
+        subjectPublicKeyP = subjectPublicKeyInfoP.getChild(1)
+
+        #Adjust for BIT STRING encapsulation
+        if (subjectPublicKeyP.value[0] !=0):
+            raise SyntaxError()
+        subjectPublicKeyP = ASN1Parser(subjectPublicKeyP.value[1:])
+
+        #Get the modulus and exponent
+        modulusP = subjectPublicKeyP.getChild(0)
+        publicExponentP = subjectPublicKeyP.getChild(1)
+
+        #Decode them into numbers
+        n = bytesToNumber(modulusP.value)
+        e = bytesToNumber(publicExponentP.value)
+
+        #Create a public key instance
+        self.publicKey = _createPublicRSAKey(n, e)
+
+    def getFingerprint(self):
+        """Get the hex-encoded fingerprint of this certificate.
+
+        @rtype: str
+        @return: A hex-encoded fingerprint.
+        """
+        return sha.sha(self.bytes).hexdigest()
+
+    def getCommonName(self):
+        """Get the Subject's Common Name from the certificate.
+
+        The cryptlib_py module must be installed in order to use this
+        function.
+
+        @rtype: str or None
+        @return: The CN component of the certificate's subject DN, if
+        present.
+        """
+        import cryptlib_py
+        import array
+        c = cryptlib_py.cryptImportCert(self.bytes, cryptlib_py.CRYPT_UNUSED)
+        name = cryptlib_py.CRYPT_CERTINFO_COMMONNAME
+        try:
+            try:
+                length = cryptlib_py.cryptGetAttributeString(c, name, None)
+                returnVal = array.array('B', [0] * length)
+                cryptlib_py.cryptGetAttributeString(c, name, returnVal)
+                returnVal = returnVal.tostring()
+            except cryptlib_py.CryptException, e:
+                if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND:
+                    returnVal = None
+            return returnVal
+        finally:
+            cryptlib_py.cryptDestroyCert(c)
+
+    def writeBytes(self):
+        return self.bytes
+
+

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/X509CertChain.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/X509CertChain.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,181 @@
+"""Class representing an X.509 certificate chain."""
+
+from utils import cryptomath
+
+class X509CertChain:
+    """This class represents a chain of X.509 certificates.
+
+    @type x509List: list
+    @ivar x509List: A list of L{tlslite.X509.X509} instances,
+    starting with the end-entity certificate and with every
+    subsequent certificate certifying the previous.
+    """
+
+    def __init__(self, x509List=None):
+        """Create a new X509CertChain.
+
+        @type x509List: list
+        @param x509List: A list of L{tlslite.X509.X509} instances,
+        starting with the end-entity certificate and with every
+        subsequent certificate certifying the previous.
+        """
+        if x509List:
+            self.x509List = x509List
+        else:
+            self.x509List = []
+
+    def getNumCerts(self):
+        """Get the number of certificates in this chain.
+
+        @rtype: int
+        """
+        return len(self.x509List)
+
+    def getEndEntityPublicKey(self):
+        """Get the public key from the end-entity certificate.
+
+        @rtype: L{tlslite.utils.RSAKey.RSAKey}
+        """
+        if self.getNumCerts() == 0:
+            raise AssertionError()
+        return self.x509List[0].publicKey
+
+    def getFingerprint(self):
+        """Get the hex-encoded fingerprint of the end-entity certificate.
+
+        @rtype: str
+        @return: A hex-encoded fingerprint.
+        """
+        if self.getNumCerts() == 0:
+            raise AssertionError()
+        return self.x509List[0].getFingerprint()
+
+    def getCommonName(self):
+        """Get the Subject's Common Name from the end-entity certificate.
+
+        The cryptlib_py module must be installed in order to use this
+        function.
+
+        @rtype: str or None
+        @return: The CN component of the certificate's subject DN, if
+        present.
+        """
+        if self.getNumCerts() == 0:
+            raise AssertionError()
+        return self.x509List[0].getCommonName()
+
+    def validate(self, x509TrustList):
+        """Check the validity of the certificate chain.
+
+        This checks that every certificate in the chain validates with
+        the subsequent one, until some certificate validates with (or
+        is identical to) one of the passed-in root certificates.
+
+        The cryptlib_py module must be installed in order to use this
+        function.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        certificate chain must extend to one of these certificates to
+        be considered valid.
+        """
+
+        import cryptlib_py
+        c1 = None
+        c2 = None
+        lastC = None
+        rootC = None
+
+        try:
+            rootFingerprints = [c.getFingerprint() for c in x509TrustList]
+
+            #Check that every certificate in the chain validates with the
+            #next one
+            for cert1, cert2 in zip(self.x509List, self.x509List[1:]):
+
+                #If we come upon a root certificate, we're done.
+                if cert1.getFingerprint() in rootFingerprints:
+                    return True
+
+                c1 = cryptlib_py.cryptImportCert(cert1.writeBytes(),
+                                                 cryptlib_py.CRYPT_UNUSED)
+                c2 = cryptlib_py.cryptImportCert(cert2.writeBytes(),
+                                                 cryptlib_py.CRYPT_UNUSED)
+                try:
+                    cryptlib_py.cryptCheckCert(c1, c2)
+                except:
+                    return False
+                cryptlib_py.cryptDestroyCert(c1)
+                c1 = None
+                cryptlib_py.cryptDestroyCert(c2)
+                c2 = None
+
+            #If the last certificate is one of the root certificates, we're
+            #done.
+            if self.x509List[-1].getFingerprint() in rootFingerprints:
+                return True
+
+            #Otherwise, find a root certificate that the last certificate
+            #chains to, and validate them.
+            lastC = cryptlib_py.cryptImportCert(self.x509List[-1].writeBytes(),
+                                                cryptlib_py.CRYPT_UNUSED)
+            for rootCert in x509TrustList:
+                rootC = cryptlib_py.cryptImportCert(rootCert.writeBytes(),
+                                                    cryptlib_py.CRYPT_UNUSED)
+                if self._checkChaining(lastC, rootC):
+                    try:
+                        cryptlib_py.cryptCheckCert(lastC, rootC)
+                        return True
+                    except:
+                        return False
+            return False
+        finally:
+            if not (c1 is None):
+                cryptlib_py.cryptDestroyCert(c1)
+            if not (c2 is None):
+                cryptlib_py.cryptDestroyCert(c2)
+            if not (lastC is None):
+                cryptlib_py.cryptDestroyCert(lastC)
+            if not (rootC is None):
+                cryptlib_py.cryptDestroyCert(rootC)
+
+
+
+    def _checkChaining(self, lastC, rootC):
+        import cryptlib_py
+        import array
+        def compareNames(name):
+            try:
+                length = cryptlib_py.cryptGetAttributeString(lastC, name, None)
+                lastName = array.array('B', [0] * length)
+                cryptlib_py.cryptGetAttributeString(lastC, name, lastName)
+                lastName = lastName.tostring()
+            except cryptlib_py.CryptException, e:
+                if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND:
+                    lastName = None
+            try:
+                length = cryptlib_py.cryptGetAttributeString(rootC, name, None)
+                rootName = array.array('B', [0] * length)
+                cryptlib_py.cryptGetAttributeString(rootC, name, rootName)
+                rootName = rootName.tostring()
+            except cryptlib_py.CryptException, e:
+                if e[0] == cryptlib_py.CRYPT_ERROR_NOTFOUND:
+                    rootName = None
+
+            return lastName == rootName
+
+        cryptlib_py.cryptSetAttribute(lastC,
+                                      cryptlib_py.CRYPT_CERTINFO_ISSUERNAME,
+                                      cryptlib_py.CRYPT_UNUSED)
+
+        if not compareNames(cryptlib_py.CRYPT_CERTINFO_COUNTRYNAME):
+            return False
+        if not compareNames(cryptlib_py.CRYPT_CERTINFO_LOCALITYNAME):
+            return False
+        if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONNAME):
+            return False
+        if not compareNames(cryptlib_py.CRYPT_CERTINFO_ORGANIZATIONALUNITNAME):
+            return False
+        if not compareNames(cryptlib_py.CRYPT_CERTINFO_COMMONNAME):
+            return False
+        return True
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,39 @@
+"""
+TLS Lite is a free python library that implements SSL v3, TLS v1, and
+TLS v1.1.  TLS Lite supports non-traditional authentication methods
+such as SRP, shared keys, and cryptoIDs, in addition to X.509
+certificates.  TLS Lite is pure python, however it can access OpenSSL,
+cryptlib, pycrypto, and GMPY for faster crypto operations.  TLS Lite
+integrates with httplib, xmlrpclib, poplib, imaplib, smtplib,
+SocketServer, asyncore, and Twisted.
+
+To use, do::
+
+    from tlslite.api import *
+
+Then use the L{tlslite.TLSConnection.TLSConnection} class with a socket,
+or use one of the integration classes in L{tlslite.integration}.
+
+ version: 0.3.8
+"""
+__version__ = "0.3.8"
+
+__all__ = ["api",
+           "BaseDB",
+           "Checker",
+           "constants",
+           "errors",
+           "FileObject",
+           "HandshakeSettings",
+           "mathtls",
+           "messages",
+           "Session",
+           "SessionCache",
+           "SharedKeyDB",
+           "TLSConnection",
+           "TLSRecordLayer",
+           "VerifierDB",
+           "X509",
+           "X509CertChain",
+           "integration",
+           "utils"]

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/api.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/api.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,75 @@
+"""Import this module for easy access to TLS Lite objects.
+
+The TLS Lite API consists of classes, functions, and variables spread
+throughout this package.  Instead of importing them individually with::
+
+    from tlslite.TLSConnection import TLSConnection
+    from tlslite.HandshakeSettings import HandshakeSettings
+    from tlslite.errors import *
+    .
+    .
+
+It's easier to do::
+
+    from tlslite.api import *
+
+This imports all the important objects (TLSConnection, Checker,
+HandshakeSettings, etc.) into the global namespace.  In particular, it
+imports::
+
+    from constants import AlertLevel, AlertDescription, Fault
+    from errors import *
+    from Checker import Checker
+    from HandshakeSettings import HandshakeSettings
+    from Session import Session
+    from SessionCache import SessionCache
+    from SharedKeyDB import SharedKeyDB
+    from TLSConnection import TLSConnection
+    from VerifierDB import VerifierDB
+    from X509 import X509
+    from X509CertChain import X509CertChain
+
+    from integration.HTTPTLSConnection import HTTPTLSConnection
+    from integration.POP3_TLS import POP3_TLS
+    from integration.IMAP4_TLS import IMAP4_TLS
+    from integration.SMTP_TLS import SMTP_TLS
+    from integration.XMLRPCTransport import XMLRPCTransport
+    from integration.TLSSocketServerMixIn import TLSSocketServerMixIn
+    from integration.TLSAsyncDispatcherMixIn import TLSAsyncDispatcherMixIn
+    from integration.TLSTwistedProtocolWrapper import TLSTwistedProtocolWrapper
+    from utils.cryptomath import cryptlibpyLoaded, m2cryptoLoaded,
+                                 gmpyLoaded, pycryptoLoaded, prngName
+    from utils.keyfactory import generateRSAKey, parsePEMKey, parseXMLKey,
+                                 parseAsPublicKey, parsePrivateKey
+"""
+
+from constants import AlertLevel, AlertDescription, Fault
+from errors import *
+from Checker import Checker
+from HandshakeSettings import HandshakeSettings
+from Session import Session
+from SessionCache import SessionCache
+from SharedKeyDB import SharedKeyDB
+from TLSConnection import TLSConnection
+from VerifierDB import VerifierDB
+from X509 import X509
+from X509CertChain import X509CertChain
+
+from integration.HTTPTLSConnection import HTTPTLSConnection
+from integration.TLSSocketServerMixIn import TLSSocketServerMixIn
+from integration.TLSAsyncDispatcherMixIn import TLSAsyncDispatcherMixIn
+from integration.POP3_TLS import POP3_TLS
+from integration.IMAP4_TLS import IMAP4_TLS
+from integration.SMTP_TLS import SMTP_TLS
+from integration.XMLRPCTransport import XMLRPCTransport
+try:
+    import twisted
+    del(twisted)
+    from integration.TLSTwistedProtocolWrapper import TLSTwistedProtocolWrapper
+except ImportError:
+    pass
+
+from utils.cryptomath import cryptlibpyLoaded, m2cryptoLoaded, gmpyLoaded, \
+                             pycryptoLoaded, prngName
+from utils.keyfactory import generateRSAKey, parsePEMKey, parseXMLKey, \
+                             parseAsPublicKey, parsePrivateKey

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/constants.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/constants.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,225 @@
+"""Constants used in various places."""
+
+class CertificateType:
+    x509 = 0
+    openpgp = 1
+    cryptoID = 2
+
+class HandshakeType:
+    hello_request = 0
+    client_hello = 1
+    server_hello = 2
+    certificate = 11
+    server_key_exchange = 12
+    certificate_request = 13
+    server_hello_done = 14
+    certificate_verify = 15
+    client_key_exchange = 16
+    finished = 20
+
+class ContentType:
+    change_cipher_spec = 20
+    alert = 21
+    handshake = 22
+    application_data = 23
+    all = (20,21,22,23)
+
+class AlertLevel:
+    warning = 1
+    fatal = 2
+
+class AlertDescription:
+    """
+    @cvar bad_record_mac: A TLS record failed to decrypt properly.
+
+    If this occurs during a shared-key or SRP handshake it most likely
+    indicates a bad password.  It may also indicate an implementation
+    error, or some tampering with the data in transit.
+
+    This alert will be signalled by the server if the SRP password is bad.  It
+    may also be signalled by the server if the SRP username is unknown to the
+    server, but it doesn't wish to reveal that fact.
+
+    This alert will be signalled by the client if the shared-key username is
+    bad.
+
+    @cvar handshake_failure: A problem occurred while handshaking.
+
+    This typically indicates a lack of common ciphersuites between client and
+    server, or some other disagreement (about SRP parameters or key sizes,
+    for example).
+
+    @cvar protocol_version: The other party's SSL/TLS version was unacceptable.
+
+    This indicates that the client and server couldn't agree on which version
+    of SSL or TLS to use.
+
+    @cvar user_canceled: The handshake is being cancelled for some reason.
+
+    """
+
+    close_notify = 0
+    unexpected_message = 10
+    bad_record_mac = 20
+    decryption_failed = 21
+    record_overflow = 22
+    decompression_failure = 30
+    handshake_failure = 40
+    no_certificate = 41 #SSLv3
+    bad_certificate = 42
+    unsupported_certificate = 43
+    certificate_revoked = 44
+    certificate_expired = 45
+    certificate_unknown = 46
+    illegal_parameter = 47
+    unknown_ca = 48
+    access_denied = 49
+    decode_error = 50
+    decrypt_error = 51
+    export_restriction = 60
+    protocol_version = 70
+    insufficient_security = 71
+    internal_error = 80
+    user_canceled = 90
+    no_renegotiation = 100
+    unknown_srp_username = 120
+    missing_srp_username = 121
+    untrusted_srp_parameters = 122
+
+class CipherSuite:
+    TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA  = 0x0050
+    TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0x0053
+    TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0x0056
+
+    TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0x0051
+    TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0x0054
+    TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0x0057
+
+    TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A
+    TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F
+    TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035
+    TLS_RSA_WITH_RC4_128_SHA = 0x0005
+
+    srpSuites = []
+    srpSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA)
+    srpSuites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA)
+    srpSuites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA)
+    def getSrpSuites(ciphers):
+        suites = []
+        for cipher in ciphers:
+            if cipher == "aes128":
+                suites.append(CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA)
+            elif cipher == "aes256":
+                suites.append(CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA)
+            elif cipher == "3des":
+                suites.append(CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA)
+        return suites
+    getSrpSuites = staticmethod(getSrpSuites)
+
+    srpRsaSuites = []
+    srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA)
+    srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA)
+    srpRsaSuites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA)
+    def getSrpRsaSuites(ciphers):
+        suites = []
+        for cipher in ciphers:
+            if cipher == "aes128":
+                suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA)
+            elif cipher == "aes256":
+                suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA)
+            elif cipher == "3des":
+                suites.append(CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA)
+        return suites
+    getSrpRsaSuites = staticmethod(getSrpRsaSuites)
+
+    rsaSuites = []
+    rsaSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA)
+    rsaSuites.append(TLS_RSA_WITH_AES_128_CBC_SHA)
+    rsaSuites.append(TLS_RSA_WITH_AES_256_CBC_SHA)
+    rsaSuites.append(TLS_RSA_WITH_RC4_128_SHA)
+    def getRsaSuites(ciphers):
+        suites = []
+        for cipher in ciphers:
+            if cipher == "aes128":
+                suites.append(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA)
+            elif cipher == "aes256":
+                suites.append(CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA)
+            elif cipher == "rc4":
+                suites.append(CipherSuite.TLS_RSA_WITH_RC4_128_SHA)
+            elif cipher == "3des":
+                suites.append(CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
+        return suites
+    getRsaSuites = staticmethod(getRsaSuites)
+
+    tripleDESSuites = []
+    tripleDESSuites.append(TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA)
+    tripleDESSuites.append(TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA)
+    tripleDESSuites.append(TLS_RSA_WITH_3DES_EDE_CBC_SHA)
+
+    aes128Suites = []
+    aes128Suites.append(TLS_SRP_SHA_WITH_AES_128_CBC_SHA)
+    aes128Suites.append(TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA)
+    aes128Suites.append(TLS_RSA_WITH_AES_128_CBC_SHA)
+
+    aes256Suites = []
+    aes256Suites.append(TLS_SRP_SHA_WITH_AES_256_CBC_SHA)
+    aes256Suites.append(TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA)
+    aes256Suites.append(TLS_RSA_WITH_AES_256_CBC_SHA)
+
+    rc4Suites = []
+    rc4Suites.append(TLS_RSA_WITH_RC4_128_SHA)
+
+
+class Fault:
+    badUsername = 101
+    badPassword = 102
+    badA = 103
+    clientSrpFaults = range(101,104)
+
+    badVerifyMessage = 601
+    clientCertFaults = range(601,602)
+
+    badPremasterPadding = 501
+    shortPremasterSecret = 502
+    clientNoAuthFaults = range(501,503)
+
+    badIdentifier = 401
+    badSharedKey = 402
+    clientSharedKeyFaults = range(401,403)
+
+    badB = 201
+    serverFaults = range(201,202)
+
+    badFinished = 300
+    badMAC = 301
+    badPadding = 302
+    genericFaults = range(300,303)
+
+    faultAlerts = {\
+        badUsername: (AlertDescription.unknown_srp_username, \
+                      AlertDescription.bad_record_mac),\
+        badPassword: (AlertDescription.bad_record_mac,),\
+        badA: (AlertDescription.illegal_parameter,),\
+        badIdentifier: (AlertDescription.handshake_failure,),\
+        badSharedKey: (AlertDescription.bad_record_mac,),\
+        badPremasterPadding: (AlertDescription.bad_record_mac,),\
+        shortPremasterSecret: (AlertDescription.bad_record_mac,),\
+        badVerifyMessage: (AlertDescription.decrypt_error,),\
+        badFinished: (AlertDescription.decrypt_error,),\
+        badMAC: (AlertDescription.bad_record_mac,),\
+        badPadding: (AlertDescription.bad_record_mac,)
+        }
+
+    faultNames = {\
+        badUsername: "bad username",\
+        badPassword: "bad password",\
+        badA: "bad A",\
+        badIdentifier: "bad identifier",\
+        badSharedKey: "bad sharedkey",\
+        badPremasterPadding: "bad premaster padding",\
+        shortPremasterSecret: "short premaster secret",\
+        badVerifyMessage: "bad verify message",\
+        badFinished: "bad finished message",\
+        badMAC: "bad MAC",\
+        badPadding: "bad padding"
+        }

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/errors.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/errors.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,149 @@
+"""Exception classes.
+ sort: TLSError, TLSAbruptCloseError, TLSAlert, TLSLocalAlert, TLSRemoteAlert,
+TLSAuthenticationError, TLSNoAuthenticationError, TLSAuthenticationTypeError,
+TLSFingerprintError, TLSAuthorizationError, TLSValidationError, TLSFaultError
+"""
+
+from constants import AlertDescription, AlertLevel
+
+class TLSError(Exception):
+    """Base class for all TLS Lite exceptions."""
+    pass
+
+class TLSAbruptCloseError(TLSError):
+    """The socket was closed without a proper TLS shutdown.
+
+    The TLS specification mandates that an alert of some sort
+    must be sent before the underlying socket is closed.  If the socket
+    is closed without this, it could signify that an attacker is trying
+    to truncate the connection.  It could also signify a misbehaving
+    TLS implementation, or a random network failure.
+    """
+    pass
+
+class TLSAlert(TLSError):
+    """A TLS alert has been signalled."""
+    pass
+
+    _descriptionStr = {\
+        AlertDescription.close_notify: "close_notify",\
+        AlertDescription.unexpected_message: "unexpected_message",\
+        AlertDescription.bad_record_mac: "bad_record_mac",\
+        AlertDescription.decryption_failed: "decryption_failed",\
+        AlertDescription.record_overflow: "record_overflow",\
+        AlertDescription.decompression_failure: "decompression_failure",\
+        AlertDescription.handshake_failure: "handshake_failure",\
+        AlertDescription.no_certificate: "no certificate",\
+        AlertDescription.bad_certificate: "bad_certificate",\
+        AlertDescription.unsupported_certificate: "unsupported_certificate",\
+        AlertDescription.certificate_revoked: "certificate_revoked",\
+        AlertDescription.certificate_expired: "certificate_expired",\
+        AlertDescription.certificate_unknown: "certificate_unknown",\
+        AlertDescription.illegal_parameter: "illegal_parameter",\
+        AlertDescription.unknown_ca: "unknown_ca",\
+        AlertDescription.access_denied: "access_denied",\
+        AlertDescription.decode_error: "decode_error",\
+        AlertDescription.decrypt_error: "decrypt_error",\
+        AlertDescription.export_restriction: "export_restriction",\
+        AlertDescription.protocol_version: "protocol_version",\
+        AlertDescription.insufficient_security: "insufficient_security",\
+        AlertDescription.internal_error: "internal_error",\
+        AlertDescription.user_canceled: "user_canceled",\
+        AlertDescription.no_renegotiation: "no_renegotiation",\
+        AlertDescription.unknown_srp_username: "unknown_srp_username",\
+        AlertDescription.missing_srp_username: "missing_srp_username"}
+
+class TLSLocalAlert(TLSAlert):
+    """A TLS alert has been signalled by the local implementation.
+
+    @type description: int
+    @ivar description: Set to one of the constants in
+    L{tlslite.constants.AlertDescription}
+
+    @type level: int
+    @ivar level: Set to one of the constants in
+    L{tlslite.constants.AlertLevel}
+
+    @type message: str
+    @ivar message: Description of what went wrong.
+    """
+    def __init__(self, alert, message=None):
+        self.description = alert.description
+        self.level = alert.level
+        self.message = message
+
+    def __str__(self):
+        alertStr = TLSAlert._descriptionStr.get(self.description)
+        if alertStr == None:
+            alertStr = str(self.description)
+        if self.message:
+            return alertStr + ": " + self.message
+        else:
+            return alertStr
+
+class TLSRemoteAlert(TLSAlert):
+    """A TLS alert has been signalled by the remote implementation.
+
+    @type description: int
+    @ivar description: Set to one of the constants in
+    L{tlslite.constants.AlertDescription}
+
+    @type level: int
+    @ivar level: Set to one of the constants in
+    L{tlslite.constants.AlertLevel}
+    """
+    def __init__(self, alert):
+        self.description = alert.description
+        self.level = alert.level
+
+    def __str__(self):
+        alertStr = TLSAlert._descriptionStr.get(self.description)
+        if alertStr == None:
+            alertStr = str(self.description)
+        return alertStr
+
+class TLSAuthenticationError(TLSError):
+    """The handshake succeeded, but the other party's authentication
+    was inadequate.
+
+    This exception will only be raised when a
+    L{tlslite.Checker.Checker} has been passed to a handshake function.
+    The Checker will be invoked once the handshake completes, and if
+    the Checker objects to how the other party authenticated, a
+    subclass of this exception will be raised.
+    """
+    pass
+
+class TLSNoAuthenticationError(TLSAuthenticationError):
+    """The Checker was expecting the other party to authenticate with a
+    certificate chain, but this did not occur."""
+    pass
+
+class TLSAuthenticationTypeError(TLSAuthenticationError):
+    """The Checker was expecting the other party to authenticate with a
+    different type of certificate chain."""
+    pass
+
+class TLSFingerprintError(TLSAuthenticationError):
+    """The Checker was expecting the other party to authenticate with a
+    certificate chain that matches a different fingerprint."""
+    pass
+
+class TLSAuthorizationError(TLSAuthenticationError):
+    """The Checker was expecting the other party to authenticate with a
+    certificate chain that has a different authorization."""
+    pass
+
+class TLSValidationError(TLSAuthenticationError):
+    """The Checker has determined that the other party's certificate
+    chain is invalid."""
+    pass
+
+class TLSFaultError(TLSError):
+    """The other party responded incorrectly to an induced fault.
+
+    This exception will only occur during fault testing, when a
+    TLSConnection's fault variable is set to induce some sort of
+    faulty behavior, and the other party doesn't respond appropriately.
+    """
+    pass

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/AsyncStateMachine.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/AsyncStateMachine.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,235 @@
+"""
+A state machine for using TLS Lite with asynchronous I/O.
+"""
+
+class AsyncStateMachine:
+    """
+    This is an abstract class that's used to integrate TLS Lite with
+    asyncore and Twisted.
+
+    This class signals wantsReadsEvent() and wantsWriteEvent().  When
+    the underlying socket has become readable or writeable, the event
+    should be passed to this class by calling inReadEvent() or
+    inWriteEvent().  This class will then try to read or write through
+    the socket, and will update its state appropriately.
+
+    This class will forward higher-level events to its subclass.  For
+    example, when a complete TLS record has been received,
+    outReadEvent() will be called with the decrypted data.
+    """
+
+    def __init__(self):
+        self._clear()
+
+    def _clear(self):
+        #These store the various asynchronous operations (i.e.
+        #generators).  Only one of them, at most, is ever active at a
+        #time.
+        self.handshaker = None
+        self.closer = None
+        self.reader = None
+        self.writer = None
+
+        #This stores the result from the last call to the
+        #currently active operation.  If 0 it indicates that the
+        #operation wants to read, if 1 it indicates that the
+        #operation wants to write.  If None, there is no active
+        #operation.
+        self.result = None
+
+    def _checkAssert(self, maxActive=1):
+        #This checks that only one operation, at most, is
+        #active, and that self.result is set appropriately.
+        activeOps = 0
+        if self.handshaker:
+            activeOps += 1
+        if self.closer:
+            activeOps += 1
+        if self.reader:
+            activeOps += 1
+        if self.writer:
+            activeOps += 1
+
+        if self.result == None:
+            if activeOps != 0:
+                raise AssertionError()
+        elif self.result in (0,1):
+            if activeOps != 1:
+                raise AssertionError()
+        else:
+            raise AssertionError()
+        if activeOps > maxActive:
+            raise AssertionError()
+
+    def wantsReadEvent(self):
+        """If the state machine wants to read.
+
+        If an operation is active, this returns whether or not the
+        operation wants to read from the socket.  If an operation is
+        not active, this returns None.
+
+        @rtype: bool or None
+        @return: If the state machine wants to read.
+        """
+        if self.result != None:
+            return self.result == 0
+        return None
+
+    def wantsWriteEvent(self):
+        """If the state machine wants to write.
+
+        If an operation is active, this returns whether or not the
+        operation wants to write to the socket.  If an operation is
+        not active, this returns None.
+
+        @rtype: bool or None
+        @return: If the state machine wants to write.
+        """
+        if self.result != None:
+            return self.result == 1
+        return None
+
+    def outConnectEvent(self):
+        """Called when a handshake operation completes.
+
+        May be overridden in subclass.
+        """
+        pass
+
+    def outCloseEvent(self):
+        """Called when a close operation completes.
+
+        May be overridden in subclass.
+        """
+        pass
+
+    def outReadEvent(self, readBuffer):
+        """Called when a read operation completes.
+
+        May be overridden in subclass."""
+        pass
+
+    def outWriteEvent(self):
+        """Called when a write operation completes.
+
+        May be overridden in subclass."""
+        pass
+
+    def inReadEvent(self):
+        """Tell the state machine it can read from the socket."""
+        try:
+            self._checkAssert()
+            if self.handshaker:
+                self._doHandshakeOp()
+            elif self.closer:
+                self._doCloseOp()
+            elif self.reader:
+                self._doReadOp()
+            elif self.writer:
+                self._doWriteOp()
+            else:
+                self.reader = self.tlsConnection.readAsync(16384)
+                self._doReadOp()
+        except:
+            self._clear()
+            raise
+
+    def inWriteEvent(self):
+        """Tell the state machine it can write to the socket."""
+        try:
+            self._checkAssert()
+            if self.handshaker:
+                self._doHandshakeOp()
+            elif self.closer:
+                self._doCloseOp()
+            elif self.reader:
+                self._doReadOp()
+            elif self.writer:
+                self._doWriteOp()
+            else:
+                self.outWriteEvent()
+        except:
+            self._clear()
+            raise
+
+    def _doHandshakeOp(self):
+        try:
+            self.result = self.handshaker.next()
+        except StopIteration:
+            self.handshaker = None
+            self.result = None
+            self.outConnectEvent()
+
+    def _doCloseOp(self):
+        try:
+            self.result = self.closer.next()
+        except StopIteration:
+            self.closer = None
+            self.result = None
+            self.outCloseEvent()
+
+    def _doReadOp(self):
+        self.result = self.reader.next()
+        if not self.result in (0,1):
+            readBuffer = self.result
+            self.reader = None
+            self.result = None
+            self.outReadEvent(readBuffer)
+
+    def _doWriteOp(self):
+        try:
+            self.result = self.writer.next()
+        except StopIteration:
+            self.writer = None
+            self.result = None
+
+    def setHandshakeOp(self, handshaker):
+        """Start a handshake operation.
+
+        @type handshaker: generator
+        @param handshaker: A generator created by using one of the
+        asynchronous handshake functions (i.e. handshakeServerAsync, or
+        handshakeClientxxx(..., async=True).
+        """
+        try:
+            self._checkAssert(0)
+            self.handshaker = handshaker
+            self._doHandshakeOp()
+        except:
+            self._clear()
+            raise
+
+    def setServerHandshakeOp(self, **args):
+        """Start a handshake operation.
+
+        The arguments passed to this function will be forwarded to
+        L{tlslite.TLSConnection.TLSConnection.handshakeServerAsync}.
+        """
+        handshaker = self.tlsConnection.handshakeServerAsync(**args)
+        self.setHandshakeOp(handshaker)
+
+    def setCloseOp(self):
+        """Start a close operation.
+        """
+        try:
+            self._checkAssert(0)
+            self.closer = self.tlsConnection.closeAsync()
+            self._doCloseOp()
+        except:
+            self._clear()
+            raise
+
+    def setWriteOp(self, writeBuffer):
+        """Start a write operation.
+
+        @type writeBuffer: str
+        @param writeBuffer: The string to transmit.
+        """
+        try:
+            self._checkAssert(0)
+            self.writer = self.tlsConnection.writeAsync(writeBuffer)
+            self._doWriteOp()
+        except:
+            self._clear()
+            raise
+

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/ClientHelper.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/ClientHelper.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,163 @@
+"""
+A helper class for using TLS Lite with stdlib clients
+(httplib, xmlrpclib, imaplib, poplib).
+"""
+
+from gdata.tlslite.Checker import Checker
+
+class ClientHelper:
+    """This is a helper class used to integrate TLS Lite with various
+    TLS clients (e.g. poplib, smtplib, httplib, etc.)"""
+
+    def __init__(self,
+              username=None, password=None, sharedKey=None,
+              certChain=None, privateKey=None,
+              cryptoID=None, protocol=None,
+              x509Fingerprint=None,
+              x509TrustList=None, x509CommonName=None,
+              settings = None):
+        """
+        For client authentication, use one of these argument
+        combinations:
+         - username, password (SRP)
+         - username, sharedKey (shared-key)
+         - certChain, privateKey (certificate)
+
+        For server authentication, you can either rely on the
+        implicit mutual authentication performed by SRP or
+        shared-keys, or you can do certificate-based server
+        authentication with one of these argument combinations:
+         - cryptoID[, protocol] (requires cryptoIDlib)
+         - x509Fingerprint
+         - x509TrustList[, x509CommonName] (requires cryptlib_py)
+
+        Certificate-based server authentication is compatible with
+        SRP or certificate-based client authentication.  It is
+        not compatible with shared-keys.
+
+        The constructor does not perform the TLS handshake itself, but
+        simply stores these arguments for later.  The handshake is
+        performed only when this class needs to connect with the
+        server.  Then you should be prepared to handle TLS-specific
+        exceptions.  See the client handshake functions in
+        L{tlslite.TLSConnection.TLSConnection} for details on which
+        exceptions might be raised.
+
+        @type username: str
+        @param username: SRP or shared-key username.  Requires the
+        'password' or 'sharedKey' argument.
+
+        @type password: str
+        @param password: SRP password for mutual authentication.
+        Requires the 'username' argument.
+
+        @type sharedKey: str
+        @param sharedKey: Shared key for mutual authentication.
+        Requires the 'username' argument.
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: Certificate chain for client authentication.
+        Requires the 'privateKey' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: Private key for client authentication.
+        Requires the 'certChain' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type cryptoID: str
+        @param cryptoID: cryptoID for server authentication.  Mutually
+        exclusive with the 'x509...' arguments.
+
+        @type protocol: str
+        @param protocol: cryptoID protocol URI for server
+        authentication.  Requires the 'cryptoID' argument.
+
+        @type x509Fingerprint: str
+        @param x509Fingerprint: Hex-encoded X.509 fingerprint for
+        server authentication.  Mutually exclusive with the 'cryptoID'
+        and 'x509TrustList' arguments.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        other party must present a certificate chain which extends to
+        one of these root certificates.  The cryptlib_py module must be
+        installed to use this parameter.  Mutually exclusive with the
+        'cryptoID' and 'x509Fingerprint' arguments.
+
+        @type x509CommonName: str
+        @param x509CommonName: The end-entity certificate's 'CN' field
+        must match this value.  For a web server, this is typically a
+        server name such as 'www.amazon.com'.  Mutually exclusive with
+        the 'cryptoID' and 'x509Fingerprint' arguments.  Requires the
+        'x509TrustList' argument.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+        """
+
+        self.username = None
+        self.password = None
+        self.sharedKey = None
+        self.certChain = None
+        self.privateKey = None
+        self.checker = None
+
+        #SRP Authentication
+        if username and password and not \
+                (sharedKey or certChain or privateKey):
+            self.username = username
+            self.password = password
+
+        #Shared Key Authentication
+        elif username and sharedKey and not \
+                (password or certChain or privateKey):
+            self.username = username
+            self.sharedKey = sharedKey
+
+        #Certificate Chain Authentication
+        elif certChain and privateKey and not \
+                (username or password or sharedKey):
+            self.certChain = certChain
+            self.privateKey = privateKey
+
+        #No Authentication
+        elif not password and not username and not \
+                sharedKey and not certChain and not privateKey:
+            pass
+
+        else:
+            raise ValueError("Bad parameters")
+
+        #Authenticate the server based on its cryptoID or fingerprint
+        if sharedKey and (cryptoID or protocol or x509Fingerprint):
+            raise ValueError("Can't use shared keys with other forms of"\
+                             "authentication")
+
+        self.checker = Checker(cryptoID, protocol, x509Fingerprint,
+                               x509TrustList, x509CommonName)
+        self.settings = settings
+
+        self.tlsSession = None
+
+    def _handshake(self, tlsConnection):
+        if self.username and self.password:
+            tlsConnection.handshakeClientSRP(username=self.username,
+                                             password=self.password,
+                                             checker=self.checker,
+                                             settings=self.settings,
+                                             session=self.tlsSession)
+        elif self.username and self.sharedKey:
+            tlsConnection.handshakeClientSharedKey(username=self.username,
+                                                   sharedKey=self.sharedKey,
+                                                   settings=self.settings)
+        else:
+            tlsConnection.handshakeClientCert(certChain=self.certChain,
+                                              privateKey=self.privateKey,
+                                              checker=self.checker,
+                                              settings=self.settings,
+                                              session=self.tlsSession)
+        self.tlsSession = tlsConnection.session

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/HTTPTLSConnection.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/HTTPTLSConnection.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,169 @@
+"""TLS Lite + httplib."""
+
+import socket
+import httplib
+from gdata.tlslite.TLSConnection import TLSConnection
+from gdata.tlslite.integration.ClientHelper import ClientHelper
+
+
+class HTTPBaseTLSConnection(httplib.HTTPConnection):
+    """This abstract class provides a framework for adding TLS support
+    to httplib."""
+
+    default_port = 443
+
+    def __init__(self, host, port=None, strict=None):
+        if strict == None:
+            #Python 2.2 doesn't support strict
+            httplib.HTTPConnection.__init__(self, host, port)
+        else:
+            httplib.HTTPConnection.__init__(self, host, port, strict)
+
+    def connect(self):
+        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        if hasattr(sock, 'settimeout'):
+            sock.settimeout(10)
+        sock.connect((self.host, self.port))
+
+        #Use a TLSConnection to emulate a socket
+        self.sock = TLSConnection(sock)
+
+        #When httplib closes this, close the socket
+        self.sock.closeSocket = True
+        self._handshake(self.sock)
+
+    def _handshake(self, tlsConnection):
+        """Called to perform some sort of handshake.
+
+        This method must be overridden in a subclass to do some type of
+        handshake.  This method will be called after the socket has
+        been connected but before any data has been sent.  If this
+        method does not raise an exception, the TLS connection will be
+        considered valid.
+
+        This method may (or may not) be called every time an HTTP
+        request is performed, depending on whether the underlying HTTP
+        connection is persistent.
+
+        @type tlsConnection: L{tlslite.TLSConnection.TLSConnection}
+        @param tlsConnection: The connection to perform the handshake
+        on.
+        """
+        raise NotImplementedError()
+
+
+class HTTPTLSConnection(HTTPBaseTLSConnection, ClientHelper):
+    """This class extends L{HTTPBaseTLSConnection} to support the
+    common types of handshaking."""
+
+    def __init__(self, host, port=None,
+                 username=None, password=None, sharedKey=None,
+                 certChain=None, privateKey=None,
+                 cryptoID=None, protocol=None,
+                 x509Fingerprint=None,
+                 x509TrustList=None, x509CommonName=None,
+                 settings = None):
+        """Create a new HTTPTLSConnection.
+
+        For client authentication, use one of these argument
+        combinations:
+         - username, password (SRP)
+         - username, sharedKey (shared-key)
+         - certChain, privateKey (certificate)
+
+        For server authentication, you can either rely on the
+        implicit mutual authentication performed by SRP or
+        shared-keys, or you can do certificate-based server
+        authentication with one of these argument combinations:
+         - cryptoID[, protocol] (requires cryptoIDlib)
+         - x509Fingerprint
+         - x509TrustList[, x509CommonName] (requires cryptlib_py)
+
+        Certificate-based server authentication is compatible with
+        SRP or certificate-based client authentication.  It is
+        not compatible with shared-keys.
+
+        The constructor does not perform the TLS handshake itself, but
+        simply stores these arguments for later.  The handshake is
+        performed only when this class needs to connect with the
+        server.  Thus you should be prepared to handle TLS-specific
+        exceptions when calling methods inherited from
+        L{httplib.HTTPConnection} such as request(), connect(), and
+        send().  See the client handshake functions in
+        L{tlslite.TLSConnection.TLSConnection} for details on which
+        exceptions might be raised.
+
+        @type host: str
+        @param host: Server to connect to.
+
+        @type port: int
+        @param port: Port to connect to.
+
+        @type username: str
+        @param username: SRP or shared-key username.  Requires the
+        'password' or 'sharedKey' argument.
+
+        @type password: str
+        @param password: SRP password for mutual authentication.
+        Requires the 'username' argument.
+
+        @type sharedKey: str
+        @param sharedKey: Shared key for mutual authentication.
+        Requires the 'username' argument.
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: Certificate chain for client authentication.
+        Requires the 'privateKey' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: Private key for client authentication.
+        Requires the 'certChain' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type cryptoID: str
+        @param cryptoID: cryptoID for server authentication.  Mutually
+        exclusive with the 'x509...' arguments.
+
+        @type protocol: str
+        @param protocol: cryptoID protocol URI for server
+        authentication.  Requires the 'cryptoID' argument.
+
+        @type x509Fingerprint: str
+        @param x509Fingerprint: Hex-encoded X.509 fingerprint for
+        server authentication.  Mutually exclusive with the 'cryptoID'
+        and 'x509TrustList' arguments.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        other party must present a certificate chain which extends to
+        one of these root certificates.  The cryptlib_py module must be
+        installed to use this parameter.  Mutually exclusive with the
+        'cryptoID' and 'x509Fingerprint' arguments.
+
+        @type x509CommonName: str
+        @param x509CommonName: The end-entity certificate's 'CN' field
+        must match this value.  For a web server, this is typically a
+        server name such as 'www.amazon.com'.  Mutually exclusive with
+        the 'cryptoID' and 'x509Fingerprint' arguments.  Requires the
+        'x509TrustList' argument.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+        """
+
+        HTTPBaseTLSConnection.__init__(self, host, port)
+
+        ClientHelper.__init__(self,
+                 username, password, sharedKey,
+                 certChain, privateKey,
+                 cryptoID, protocol,
+                 x509Fingerprint,
+                 x509TrustList, x509CommonName,
+                 settings)
+
+    def _handshake(self, tlsConnection):
+        ClientHelper._handshake(self, tlsConnection)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/IMAP4_TLS.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/IMAP4_TLS.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,132 @@
+"""TLS Lite + imaplib."""
+
+import socket
+from imaplib import IMAP4
+from gdata.tlslite.TLSConnection import TLSConnection
+from gdata.tlslite.integration.ClientHelper import ClientHelper
+
+# IMAP TLS PORT
+IMAP4_TLS_PORT = 993
+
+class IMAP4_TLS(IMAP4, ClientHelper):
+    """This class extends L{imaplib.IMAP4} with TLS support."""
+
+    def __init__(self, host = '', port = IMAP4_TLS_PORT,
+                 username=None, password=None, sharedKey=None,
+                 certChain=None, privateKey=None,
+                 cryptoID=None, protocol=None,
+                 x509Fingerprint=None,
+                 x509TrustList=None, x509CommonName=None,
+                 settings=None):
+        """Create a new IMAP4_TLS.
+
+        For client authentication, use one of these argument
+        combinations:
+         - username, password (SRP)
+         - username, sharedKey (shared-key)
+         - certChain, privateKey (certificate)
+
+        For server authentication, you can either rely on the
+        implicit mutual authentication performed by SRP or
+        shared-keys, or you can do certificate-based server
+        authentication with one of these argument combinations:
+         - cryptoID[, protocol] (requires cryptoIDlib)
+         - x509Fingerprint
+         - x509TrustList[, x509CommonName] (requires cryptlib_py)
+
+        Certificate-based server authentication is compatible with
+        SRP or certificate-based client authentication.  It is
+        not compatible with shared-keys.
+
+        The caller should be prepared to handle TLS-specific
+        exceptions.  See the client handshake functions in
+        L{tlslite.TLSConnection.TLSConnection} for details on which
+        exceptions might be raised.
+
+        @type host: str
+        @param host: Server to connect to.
+
+        @type port: int
+        @param port: Port to connect to.
+
+        @type username: str
+        @param username: SRP or shared-key username.  Requires the
+        'password' or 'sharedKey' argument.
+
+        @type password: str
+        @param password: SRP password for mutual authentication.
+        Requires the 'username' argument.
+
+        @type sharedKey: str
+        @param sharedKey: Shared key for mutual authentication.
+        Requires the 'username' argument.
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: Certificate chain for client authentication.
+        Requires the 'privateKey' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: Private key for client authentication.
+        Requires the 'certChain' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type cryptoID: str
+        @param cryptoID: cryptoID for server authentication.  Mutually
+        exclusive with the 'x509...' arguments.
+
+        @type protocol: str
+        @param protocol: cryptoID protocol URI for server
+        authentication.  Requires the 'cryptoID' argument.
+
+        @type x509Fingerprint: str
+        @param x509Fingerprint: Hex-encoded X.509 fingerprint for
+        server authentication.  Mutually exclusive with the 'cryptoID'
+        and 'x509TrustList' arguments.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        other party must present a certificate chain which extends to
+        one of these root certificates.  The cryptlib_py module must be
+        installed to use this parameter.  Mutually exclusive with the
+        'cryptoID' and 'x509Fingerprint' arguments.
+
+        @type x509CommonName: str
+        @param x509CommonName: The end-entity certificate's 'CN' field
+        must match this value.  For a web server, this is typically a
+        server name such as 'www.amazon.com'.  Mutually exclusive with
+        the 'cryptoID' and 'x509Fingerprint' arguments.  Requires the
+        'x509TrustList' argument.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+        """
+
+        ClientHelper.__init__(self,
+                 username, password, sharedKey,
+                 certChain, privateKey,
+                 cryptoID, protocol,
+                 x509Fingerprint,
+                 x509TrustList, x509CommonName,
+                 settings)
+
+        IMAP4.__init__(self, host, port)
+
+
+    def open(self, host = '', port = IMAP4_TLS_PORT):
+        """Setup connection to remote server on "host:port".
+
+        This connection will be used by the routines:
+        read, readline, send, shutdown.
+        """
+        self.host = host
+        self.port = port
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.connect((host, port))
+        self.sock = TLSConnection(self.sock)
+        self.sock.closeSocket = True
+        ClientHelper._handshake(self, self.sock)
+        self.file = self.sock.makefile('rb')

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/IntegrationHelper.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/IntegrationHelper.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,52 @@
+
+class IntegrationHelper:
+
+    def __init__(self,
+              username=None, password=None, sharedKey=None,
+              certChain=None, privateKey=None,
+              cryptoID=None, protocol=None,
+              x509Fingerprint=None,
+              x509TrustList=None, x509CommonName=None,
+              settings = None):
+
+        self.username = None
+        self.password = None
+        self.sharedKey = None
+        self.certChain = None
+        self.privateKey = None
+        self.checker = None
+
+        #SRP Authentication
+        if username and password and not \
+                (sharedKey or certChain or privateKey):
+            self.username = username
+            self.password = password
+
+        #Shared Key Authentication
+        elif username and sharedKey and not \
+                (password or certChain or privateKey):
+            self.username = username
+            self.sharedKey = sharedKey
+
+        #Certificate Chain Authentication
+        elif certChain and privateKey and not \
+                (username or password or sharedKey):
+            self.certChain = certChain
+            self.privateKey = privateKey
+
+        #No Authentication
+        elif not password and not username and not \
+                sharedKey and not certChain and not privateKey:
+            pass
+
+        else:
+            raise ValueError("Bad parameters")
+
+        #Authenticate the server based on its cryptoID or fingerprint
+        if sharedKey and (cryptoID or protocol or x509Fingerprint):
+            raise ValueError("Can't use shared keys with other forms of"\
+                             "authentication")
+
+        self.checker = Checker(cryptoID, protocol, x509Fingerprint,
+                               x509TrustList, x509CommonName)
+        self.settings = settings
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/POP3_TLS.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/POP3_TLS.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,142 @@
+"""TLS Lite + poplib."""
+
+import socket
+from poplib import POP3
+from gdata.tlslite.TLSConnection import TLSConnection
+from gdata.tlslite.integration.ClientHelper import ClientHelper
+
+# POP TLS PORT
+POP3_TLS_PORT = 995
+
+class POP3_TLS(POP3, ClientHelper):
+    """This class extends L{poplib.POP3} with TLS support."""
+
+    def __init__(self, host, port = POP3_TLS_PORT,
+                 username=None, password=None, sharedKey=None,
+                 certChain=None, privateKey=None,
+                 cryptoID=None, protocol=None,
+                 x509Fingerprint=None,
+                 x509TrustList=None, x509CommonName=None,
+                 settings=None):
+        """Create a new POP3_TLS.
+
+        For client authentication, use one of these argument
+        combinations:
+         - username, password (SRP)
+         - username, sharedKey (shared-key)
+         - certChain, privateKey (certificate)
+
+        For server authentication, you can either rely on the
+        implicit mutual authentication performed by SRP or
+        shared-keys, or you can do certificate-based server
+        authentication with one of these argument combinations:
+         - cryptoID[, protocol] (requires cryptoIDlib)
+         - x509Fingerprint
+         - x509TrustList[, x509CommonName] (requires cryptlib_py)
+
+        Certificate-based server authentication is compatible with
+        SRP or certificate-based client authentication.  It is
+        not compatible with shared-keys.
+
+        The caller should be prepared to handle TLS-specific
+        exceptions.  See the client handshake functions in
+        L{tlslite.TLSConnection.TLSConnection} for details on which
+        exceptions might be raised.
+
+        @type host: str
+        @param host: Server to connect to.
+
+        @type port: int
+        @param port: Port to connect to.
+
+        @type username: str
+        @param username: SRP or shared-key username.  Requires the
+        'password' or 'sharedKey' argument.
+
+        @type password: str
+        @param password: SRP password for mutual authentication.
+        Requires the 'username' argument.
+
+        @type sharedKey: str
+        @param sharedKey: Shared key for mutual authentication.
+        Requires the 'username' argument.
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: Certificate chain for client authentication.
+        Requires the 'privateKey' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: Private key for client authentication.
+        Requires the 'certChain' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type cryptoID: str
+        @param cryptoID: cryptoID for server authentication.  Mutually
+        exclusive with the 'x509...' arguments.
+
+        @type protocol: str
+        @param protocol: cryptoID protocol URI for server
+        authentication.  Requires the 'cryptoID' argument.
+
+        @type x509Fingerprint: str
+        @param x509Fingerprint: Hex-encoded X.509 fingerprint for
+        server authentication.  Mutually exclusive with the 'cryptoID'
+        and 'x509TrustList' arguments.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        other party must present a certificate chain which extends to
+        one of these root certificates.  The cryptlib_py module must be
+        installed to use this parameter.  Mutually exclusive with the
+        'cryptoID' and 'x509Fingerprint' arguments.
+
+        @type x509CommonName: str
+        @param x509CommonName: The end-entity certificate's 'CN' field
+        must match this value.  For a web server, this is typically a
+        server name such as 'www.amazon.com'.  Mutually exclusive with
+        the 'cryptoID' and 'x509Fingerprint' arguments.  Requires the
+        'x509TrustList' argument.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+        """
+
+        self.host = host
+        self.port = port
+        msg = "getaddrinfo returns an empty list"
+        self.sock = None
+        for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
+            af, socktype, proto, canonname, sa = res
+            try:
+                self.sock = socket.socket(af, socktype, proto)
+                self.sock.connect(sa)
+            except socket.error, msg:
+                if self.sock:
+                    self.sock.close()
+                self.sock = None
+                continue
+            break
+        if not self.sock:
+            raise socket.error, msg
+
+        ### New code below (all else copied from poplib)
+        ClientHelper.__init__(self,
+                 username, password, sharedKey,
+                 certChain, privateKey,
+                 cryptoID, protocol,
+                 x509Fingerprint,
+                 x509TrustList, x509CommonName,
+                 settings)
+
+        self.sock = TLSConnection(self.sock)
+        self.sock.closeSocket = True
+        ClientHelper._handshake(self, self.sock)
+        ###
+
+        self.file = self.sock.makefile('rb')
+        self._debugging = 0
+        self.welcome = self._getresp()

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/SMTP_TLS.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/SMTP_TLS.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,114 @@
+"""TLS Lite + smtplib."""
+
+from smtplib import SMTP
+from gdata.tlslite.TLSConnection import TLSConnection
+from gdata.tlslite.integration.ClientHelper import ClientHelper
+
+class SMTP_TLS(SMTP):
+    """This class extends L{smtplib.SMTP} with TLS support."""
+
+    def starttls(self,
+                 username=None, password=None, sharedKey=None,
+                 certChain=None, privateKey=None,
+                 cryptoID=None, protocol=None,
+                 x509Fingerprint=None,
+                 x509TrustList=None, x509CommonName=None,
+                 settings=None):
+        """Puts the connection to the SMTP server into TLS mode.
+
+        If the server supports TLS, this will encrypt the rest of the SMTP
+        session.
+
+        For client authentication, use one of these argument
+        combinations:
+         - username, password (SRP)
+         - username, sharedKey (shared-key)
+         - certChain, privateKey (certificate)
+
+        For server authentication, you can either rely on the
+        implicit mutual authentication performed by SRP or
+        shared-keys, or you can do certificate-based server
+        authentication with one of these argument combinations:
+         - cryptoID[, protocol] (requires cryptoIDlib)
+         - x509Fingerprint
+         - x509TrustList[, x509CommonName] (requires cryptlib_py)
+
+        Certificate-based server authentication is compatible with
+        SRP or certificate-based client authentication.  It is
+        not compatible with shared-keys.
+
+        The caller should be prepared to handle TLS-specific
+        exceptions.  See the client handshake functions in
+        L{tlslite.TLSConnection.TLSConnection} for details on which
+        exceptions might be raised.
+
+        @type username: str
+        @param username: SRP or shared-key username.  Requires the
+        'password' or 'sharedKey' argument.
+
+        @type password: str
+        @param password: SRP password for mutual authentication.
+        Requires the 'username' argument.
+
+        @type sharedKey: str
+        @param sharedKey: Shared key for mutual authentication.
+        Requires the 'username' argument.
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: Certificate chain for client authentication.
+        Requires the 'privateKey' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: Private key for client authentication.
+        Requires the 'certChain' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type cryptoID: str
+        @param cryptoID: cryptoID for server authentication.  Mutually
+        exclusive with the 'x509...' arguments.
+
+        @type protocol: str
+        @param protocol: cryptoID protocol URI for server
+        authentication.  Requires the 'cryptoID' argument.
+
+        @type x509Fingerprint: str
+        @param x509Fingerprint: Hex-encoded X.509 fingerprint for
+        server authentication.  Mutually exclusive with the 'cryptoID'
+        and 'x509TrustList' arguments.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        other party must present a certificate chain which extends to
+        one of these root certificates.  The cryptlib_py module must be
+        installed to use this parameter.  Mutually exclusive with the
+        'cryptoID' and 'x509Fingerprint' arguments.
+
+        @type x509CommonName: str
+        @param x509CommonName: The end-entity certificate's 'CN' field
+        must match this value.  For a web server, this is typically a
+        server name such as 'www.amazon.com'.  Mutually exclusive with
+        the 'cryptoID' and 'x509Fingerprint' arguments.  Requires the
+        'x509TrustList' argument.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+        """
+        (resp, reply) = self.docmd("STARTTLS")
+        if resp == 220:
+            helper = ClientHelper(
+                     username, password, sharedKey,
+                     certChain, privateKey,
+                     cryptoID, protocol,
+                     x509Fingerprint,
+                     x509TrustList, x509CommonName,
+                     settings)
+            conn = TLSConnection(self.sock)
+            conn.closeSocket = True
+            helper._handshake(conn)
+            self.sock = conn
+            self.file = conn.makefile('rb')
+        return (resp, reply)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSAsyncDispatcherMixIn.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,139 @@
+"""TLS Lite + asyncore."""
+
+
+import asyncore
+from gdata.tlslite.TLSConnection import TLSConnection
+from AsyncStateMachine import AsyncStateMachine
+
+
+class TLSAsyncDispatcherMixIn(AsyncStateMachine):
+    """This class can be "mixed in" with an
+    L{asyncore.dispatcher} to add TLS support.
+
+    This class essentially sits between the dispatcher and the select
+    loop, intercepting events and only calling the dispatcher when
+    applicable.
+
+    In the case of handle_read(), a read operation will be activated,
+    and when it completes, the bytes will be placed in a buffer where
+    the dispatcher can retrieve them by calling recv(), and the
+    dispatcher's handle_read() will be called.
+
+    In the case of handle_write(), the dispatcher's handle_write() will
+    be called, and when it calls send(), a write operation will be
+    activated.
+
+    To use this class, you must combine it with an asyncore.dispatcher,
+    and pass in a handshake operation with setServerHandshakeOp().
+
+    Below is an example of using this class with medusa.  This class is
+    mixed in with http_channel to create http_tls_channel.  Note:
+     1. the mix-in is listed first in the inheritance list
+
+     2. the input buffer size must be at least 16K, otherwise the
+       dispatcher might not read all the bytes from the TLS layer,
+       leaving some bytes in limbo.
+
+     3. IE seems to have a problem receiving a whole HTTP response in a
+     single TLS record, so HTML pages containing '\\r\\n\\r\\n' won't
+     be displayed on IE.
+
+    Add the following text into 'start_medusa.py', in the 'HTTP Server'
+    section::
+
+        from tlslite.api import *
+        s = open("./serverX509Cert.pem").read()
+        x509 = X509()
+        x509.parse(s)
+        certChain = X509CertChain([x509])
+
+        s = open("./serverX509Key.pem").read()
+        privateKey = parsePEMKey(s, private=True)
+
+        class http_tls_channel(TLSAsyncDispatcherMixIn,
+                               http_server.http_channel):
+            ac_in_buffer_size = 16384
+
+            def __init__ (self, server, conn, addr):
+                http_server.http_channel.__init__(self, server, conn, addr)
+                TLSAsyncDispatcherMixIn.__init__(self, conn)
+                self.tlsConnection.ignoreAbruptClose = True
+                self.setServerHandshakeOp(certChain=certChain,
+                                          privateKey=privateKey)
+
+        hs.channel_class = http_tls_channel
+
+    If the TLS layer raises an exception, the exception will be caught
+    in asyncore.dispatcher, which will call close() on this class.  The
+    TLS layer always closes the TLS connection before raising an
+    exception, so the close operation will complete right away, causing
+    asyncore.dispatcher.close() to be called, which closes the socket
+    and removes this instance from the asyncore loop.
+
+    """
+
+
+    def __init__(self, sock=None):
+        AsyncStateMachine.__init__(self)
+
+        if sock:
+            self.tlsConnection = TLSConnection(sock)
+
+        #Calculate the sibling I'm being mixed in with.
+        #This is necessary since we override functions
+        #like readable(), handle_read(), etc., but we
+        #also want to call the sibling's versions.
+        for cl in self.__class__.__bases__:
+            if cl != TLSAsyncDispatcherMixIn and cl != AsyncStateMachine:
+                self.siblingClass = cl
+                break
+        else:
+            raise AssertionError()
+
+    def readable(self):
+        result = self.wantsReadEvent()
+        if result != None:
+            return result
+        return self.siblingClass.readable(self)
+
+    def writable(self):
+        result = self.wantsWriteEvent()
+        if result != None:
+            return result
+        return self.siblingClass.writable(self)
+
+    def handle_read(self):
+        self.inReadEvent()
+
+    def handle_write(self):
+        self.inWriteEvent()
+
+    def outConnectEvent(self):
+        self.siblingClass.handle_connect(self)
+
+    def outCloseEvent(self):
+        asyncore.dispatcher.close(self)
+
+    def outReadEvent(self, readBuffer):
+        self.readBuffer = readBuffer
+        self.siblingClass.handle_read(self)
+
+    def outWriteEvent(self):
+        self.siblingClass.handle_write(self)
+
+    def recv(self, bufferSize=16384):
+        if bufferSize < 16384 or self.readBuffer == None:
+            raise AssertionError()
+        returnValue = self.readBuffer
+        self.readBuffer = None
+        return returnValue
+
+    def send(self, writeBuffer):
+        self.setWriteOp(writeBuffer)
+        return len(writeBuffer)
+
+    def close(self):
+        if hasattr(self, "tlsConnection"):
+            self.setCloseOp()
+        else:
+            asyncore.dispatcher.close(self)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSSocketServerMixIn.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSSocketServerMixIn.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,59 @@
+"""TLS Lite + SocketServer."""
+
+from gdata.tlslite.TLSConnection import TLSConnection
+
+class TLSSocketServerMixIn:
+    """
+    This class can be mixed in with any L{SocketServer.TCPServer} to
+    add TLS support.
+
+    To use this class, define a new class that inherits from it and
+    some L{SocketServer.TCPServer} (with the mix-in first). Then
+    implement the handshake() method, doing some sort of server
+    handshake on the connection argument.  If the handshake method
+    returns True, the RequestHandler will be triggered.  Below is a
+    complete example of a threaded HTTPS server::
+
+        from SocketServer import *
+        from BaseHTTPServer import *
+        from SimpleHTTPServer import *
+        from tlslite.api import *
+
+        s = open("./serverX509Cert.pem").read()
+        x509 = X509()
+        x509.parse(s)
+        certChain = X509CertChain([x509])
+
+        s = open("./serverX509Key.pem").read()
+        privateKey = parsePEMKey(s, private=True)
+
+        sessionCache = SessionCache()
+
+        class MyHTTPServer(ThreadingMixIn, TLSSocketServerMixIn,
+                           HTTPServer):
+          def handshake(self, tlsConnection):
+              try:
+                  tlsConnection.handshakeServer(certChain=certChain,
+                                                privateKey=privateKey,
+                                                sessionCache=sessionCache)
+                  tlsConnection.ignoreAbruptClose = True
+                  return True
+              except TLSError, error:
+                  print "Handshake failure:", str(error)
+                  return False
+
+        httpd = MyHTTPServer(('localhost', 443), SimpleHTTPRequestHandler)
+        httpd.serve_forever()
+    """
+
+
+    def finish_request(self, sock, client_address):
+        tlsConnection = TLSConnection(sock)
+        if self.handshake(tlsConnection) == True:
+            self.RequestHandlerClass(tlsConnection, client_address, self)
+            tlsConnection.close()
+
+    #Implement this method to do some form of handshaking.  Return True
+    #if the handshake finishes properly and the request is authorized.
+    def handshake(self, tlsConnection):
+        raise NotImplementedError()

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/TLSTwistedProtocolWrapper.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,196 @@
+"""TLS Lite + Twisted."""
+
+from twisted.protocols.policies import ProtocolWrapper, WrappingFactory
+from twisted.python.failure import Failure
+
+from AsyncStateMachine import AsyncStateMachine
+from gdata.tlslite.TLSConnection import TLSConnection
+from gdata.tlslite.errors import *
+
+import socket
+import errno
+
+
+#The TLSConnection is created around a "fake socket" that
+#plugs it into the underlying Twisted transport
+class _FakeSocket:
+    def __init__(self, wrapper):
+        self.wrapper = wrapper
+        self.data = ""
+
+    def send(self, data):
+        ProtocolWrapper.write(self.wrapper, data)
+        return len(data)
+
+    def recv(self, numBytes):
+        if self.data == "":
+            raise socket.error, (errno.EWOULDBLOCK, "")
+        returnData = self.data[:numBytes]
+        self.data = self.data[numBytes:]
+        return returnData
+
+class TLSTwistedProtocolWrapper(ProtocolWrapper, AsyncStateMachine):
+    """This class can wrap Twisted protocols to add TLS support.
+
+    Below is a complete example of using TLS Lite with a Twisted echo
+    server.
+
+    There are two server implementations below.  Echo is the original
+    protocol, which is oblivious to TLS.  Echo1 subclasses Echo and
+    negotiates TLS when the client connects.  Echo2 subclasses Echo and
+    negotiates TLS when the client sends "STARTTLS"::
+
+        from twisted.internet.protocol import Protocol, Factory
+        from twisted.internet import reactor
+        from twisted.protocols.policies import WrappingFactory
+        from twisted.protocols.basic import LineReceiver
+        from twisted.python import log
+        from twisted.python.failure import Failure
+        import sys
+        from tlslite.api import *
+
+        s = open("./serverX509Cert.pem").read()
+        x509 = X509()
+        x509.parse(s)
+        certChain = X509CertChain([x509])
+
+        s = open("./serverX509Key.pem").read()
+        privateKey = parsePEMKey(s, private=True)
+
+        verifierDB = VerifierDB("verifierDB")
+        verifierDB.open()
+
+        class Echo(LineReceiver):
+            def connectionMade(self):
+                self.transport.write("Welcome to the echo server!\\r\\n")
+
+            def lineReceived(self, line):
+                self.transport.write(line + "\\r\\n")
+
+        class Echo1(Echo):
+            def connectionMade(self):
+                if not self.transport.tlsStarted:
+                    self.transport.setServerHandshakeOp(certChain=certChain,
+                                                        privateKey=privateKey,
+                                                        verifierDB=verifierDB)
+                else:
+                    Echo.connectionMade(self)
+
+            def connectionLost(self, reason):
+                pass #Handle any TLS exceptions here
+
+        class Echo2(Echo):
+            def lineReceived(self, data):
+                if data == "STARTTLS":
+                    self.transport.setServerHandshakeOp(certChain=certChain,
+                                                        privateKey=privateKey,
+                                                        verifierDB=verifierDB)
+                else:
+                    Echo.lineReceived(self, data)
+
+            def connectionLost(self, reason):
+                pass #Handle any TLS exceptions here
+
+        factory = Factory()
+        factory.protocol = Echo1
+        #factory.protocol = Echo2
+
+        wrappingFactory = WrappingFactory(factory)
+        wrappingFactory.protocol = TLSTwistedProtocolWrapper
+
+        log.startLogging(sys.stdout)
+        reactor.listenTCP(1079, wrappingFactory)
+        reactor.run()
+
+    This class works as follows:
+
+    Data comes in and is given to the AsyncStateMachine for handling.
+    AsyncStateMachine will forward events to this class, and we'll
+    pass them on to the ProtocolHandler, which will proxy them to the
+    wrapped protocol.  The wrapped protocol may then call back into
+    this class, and these calls will be proxied into the
+    AsyncStateMachine.
+
+    The call graph looks like this:
+     - self.dataReceived
+       - AsyncStateMachine.inReadEvent
+         - self.out(Connect|Close|Read)Event
+           - ProtocolWrapper.(connectionMade|loseConnection|dataReceived)
+             - self.(loseConnection|write|writeSequence)
+               - AsyncStateMachine.(setCloseOp|setWriteOp)
+    """
+
+    #WARNING: IF YOU COPY-AND-PASTE THE ABOVE CODE, BE SURE TO REMOVE
+    #THE EXTRA ESCAPING AROUND "\\r\\n"
+
+    def __init__(self, factory, wrappedProtocol):
+        ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+        AsyncStateMachine.__init__(self)
+        self.fakeSocket = _FakeSocket(self)
+        self.tlsConnection = TLSConnection(self.fakeSocket)
+        self.tlsStarted = False
+        self.connectionLostCalled = False
+
+    def connectionMade(self):
+        try:
+            ProtocolWrapper.connectionMade(self)
+        except TLSError, e:
+            self.connectionLost(Failure(e))
+            ProtocolWrapper.loseConnection(self)
+
+    def dataReceived(self, data):
+        try:
+            if not self.tlsStarted:
+                ProtocolWrapper.dataReceived(self, data)
+            else:
+                self.fakeSocket.data += data
+                while self.fakeSocket.data:
+                    AsyncStateMachine.inReadEvent(self)
+        except TLSError, e:
+            self.connectionLost(Failure(e))
+            ProtocolWrapper.loseConnection(self)
+
+    def connectionLost(self, reason):
+        if not self.connectionLostCalled:
+            ProtocolWrapper.connectionLost(self, reason)
+            self.connectionLostCalled = True
+
+
+    def outConnectEvent(self):
+        ProtocolWrapper.connectionMade(self)
+
+    def outCloseEvent(self):
+        ProtocolWrapper.loseConnection(self)
+
+    def outReadEvent(self, data):
+        if data == "":
+            ProtocolWrapper.loseConnection(self)
+        else:
+            ProtocolWrapper.dataReceived(self, data)
+
+
+    def setServerHandshakeOp(self, **args):
+        self.tlsStarted = True
+        AsyncStateMachine.setServerHandshakeOp(self, **args)
+
+    def loseConnection(self):
+        if not self.tlsStarted:
+            ProtocolWrapper.loseConnection(self)
+        else:
+            AsyncStateMachine.setCloseOp(self)
+
+    def write(self, data):
+        if not self.tlsStarted:
+            ProtocolWrapper.write(self, data)
+        else:
+            #Because of the FakeSocket, write operations are guaranteed to
+            #terminate immediately.
+            AsyncStateMachine.setWriteOp(self, data)
+
+    def writeSequence(self, seq):
+        if not self.tlsStarted:
+            ProtocolWrapper.writeSequence(self, seq)
+        else:
+            #Because of the FakeSocket, write operations are guaranteed to
+            #terminate immediately.
+            AsyncStateMachine.setWriteOp(self, "".join(seq))

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/XMLRPCTransport.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/XMLRPCTransport.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,137 @@
+"""TLS Lite + xmlrpclib."""
+
+import xmlrpclib
+import httplib
+from gdata.tlslite.integration.HTTPTLSConnection import HTTPTLSConnection
+from gdata.tlslite.integration.ClientHelper import ClientHelper
+
+
+class XMLRPCTransport(xmlrpclib.Transport, ClientHelper):
+    """Handles an HTTPS transaction to an XML-RPC server."""
+
+    def __init__(self,
+                 username=None, password=None, sharedKey=None,
+                 certChain=None, privateKey=None,
+                 cryptoID=None, protocol=None,
+                 x509Fingerprint=None,
+                 x509TrustList=None, x509CommonName=None,
+                 settings=None):
+        """Create a new XMLRPCTransport.
+
+        An instance of this class can be passed to L{xmlrpclib.ServerProxy}
+        to use TLS with XML-RPC calls::
+
+            from tlslite.api import XMLRPCTransport
+            from xmlrpclib import ServerProxy
+
+            transport = XMLRPCTransport(user="alice", password="abra123")
+            server = ServerProxy("https://localhost";, transport)
+
+        For client authentication, use one of these argument
+        combinations:
+         - username, password (SRP)
+         - username, sharedKey (shared-key)
+         - certChain, privateKey (certificate)
+
+        For server authentication, you can either rely on the
+        implicit mutual authentication performed by SRP or
+        shared-keys, or you can do certificate-based server
+        authentication with one of these argument combinations:
+         - cryptoID[, protocol] (requires cryptoIDlib)
+         - x509Fingerprint
+         - x509TrustList[, x509CommonName] (requires cryptlib_py)
+
+        Certificate-based server authentication is compatible with
+        SRP or certificate-based client authentication.  It is
+        not compatible with shared-keys.
+
+        The constructor does not perform the TLS handshake itself, but
+        simply stores these arguments for later.  The handshake is
+        performed only when this class needs to connect with the
+        server.  Thus you should be prepared to handle TLS-specific
+        exceptions when calling methods of L{xmlrpclib.ServerProxy}.  See the
+        client handshake functions in
+        L{tlslite.TLSConnection.TLSConnection} for details on which
+        exceptions might be raised.
+
+        @type username: str
+        @param username: SRP or shared-key username.  Requires the
+        'password' or 'sharedKey' argument.
+
+        @type password: str
+        @param password: SRP password for mutual authentication.
+        Requires the 'username' argument.
+
+        @type sharedKey: str
+        @param sharedKey: Shared key for mutual authentication.
+        Requires the 'username' argument.
+
+        @type certChain: L{tlslite.X509CertChain.X509CertChain} or
+        L{cryptoIDlib.CertChain.CertChain}
+        @param certChain: Certificate chain for client authentication.
+        Requires the 'privateKey' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type privateKey: L{tlslite.utils.RSAKey.RSAKey}
+        @param privateKey: Private key for client authentication.
+        Requires the 'certChain' argument.  Excludes the SRP or
+        shared-key related arguments.
+
+        @type cryptoID: str
+        @param cryptoID: cryptoID for server authentication.  Mutually
+        exclusive with the 'x509...' arguments.
+
+        @type protocol: str
+        @param protocol: cryptoID protocol URI for server
+        authentication.  Requires the 'cryptoID' argument.
+
+        @type x509Fingerprint: str
+        @param x509Fingerprint: Hex-encoded X.509 fingerprint for
+        server authentication.  Mutually exclusive with the 'cryptoID'
+        and 'x509TrustList' arguments.
+
+        @type x509TrustList: list of L{tlslite.X509.X509}
+        @param x509TrustList: A list of trusted root certificates.  The
+        other party must present a certificate chain which extends to
+        one of these root certificates.  The cryptlib_py module must be
+        installed to use this parameter.  Mutually exclusive with the
+        'cryptoID' and 'x509Fingerprint' arguments.
+
+        @type x509CommonName: str
+        @param x509CommonName: The end-entity certificate's 'CN' field
+        must match this value.  For a web server, this is typically a
+        server name such as 'www.amazon.com'.  Mutually exclusive with
+        the 'cryptoID' and 'x509Fingerprint' arguments.  Requires the
+        'x509TrustList' argument.
+
+        @type settings: L{tlslite.HandshakeSettings.HandshakeSettings}
+        @param settings: Various settings which can be used to control
+        the ciphersuites, certificate types, and SSL/TLS versions
+        offered by the client.
+        """
+
+        ClientHelper.__init__(self,
+                 username, password, sharedKey,
+                 certChain, privateKey,
+                 cryptoID, protocol,
+                 x509Fingerprint,
+                 x509TrustList, x509CommonName,
+                 settings)
+
+
+    def make_connection(self, host):
+        # create a HTTPS connection object from a host descriptor
+        host, extra_headers, x509 = self.get_host_info(host)
+        http = HTTPTLSConnection(host, None,
+                                 self.username, self.password,
+                                 self.sharedKey,
+                                 self.certChain, self.privateKey,
+                                 self.checker.cryptoID,
+                                 self.checker.protocol,
+                                 self.checker.x509Fingerprint,
+                                 self.checker.x509TrustList,
+                                 self.checker.x509CommonName,
+                                 self.settings)
+        http2 = httplib.HTTP()
+        http2._setup(http)
+        return http2

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/integration/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,17 @@
+"""Classes for integrating TLS Lite with other packages."""
+
+__all__ = ["AsyncStateMachine",
+           "HTTPTLSConnection",
+           "POP3_TLS",
+           "IMAP4_TLS",
+           "SMTP_TLS",
+           "XMLRPCTransport",
+           "TLSSocketServerMixIn",
+           "TLSAsyncDispatcherMixIn",
+           "TLSTwistedProtocolWrapper"]
+
+try:
+    import twisted
+    del twisted
+except ImportError:
+   del __all__[__all__.index("TLSTwistedProtocolWrapper")]

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/mathtls.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/mathtls.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,170 @@
+"""Miscellaneous helper functions."""
+
+from utils.compat import *
+from utils.cryptomath import *
+
+import hmac
+import md5
+import sha
+
+#1024, 1536, 2048, 3072, 4096, 6144, and 8192 bit groups]
+goodGroupParameters = [(2,0xEEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3),\
+                       (2,0x9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB),\
+                       (2,0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73),\
+                       (2,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF),\
+                       (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA99
 3B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF),\
+                       (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA99
 3B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF),\
+                       (5,0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA99
 3B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE38
 2BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF)]
+
+def P_hash(hashModule, secret, seed, length):
+    bytes = createByteArrayZeros(length)
+    secret = bytesToString(secret)
+    seed = bytesToString(seed)
+    A = seed
+    index = 0
+    while 1:
+        A = hmac.HMAC(secret, A, hashModule).digest()
+        output = hmac.HMAC(secret, A+seed, hashModule).digest()
+        for c in output:
+            if index >= length:
+                return bytes
+            bytes[index] = ord(c)
+            index += 1
+    return bytes
+
+def PRF(secret, label, seed, length):
+    #Split the secret into left and right halves
+    S1 = secret[ : int(math.ceil(len(secret)/2.0))]
+    S2 = secret[ int(math.floor(len(secret)/2.0)) : ]
+
+    #Run the left half through P_MD5 and the right half through P_SHA1
+    p_md5 = P_hash(md5, S1, concatArrays(stringToBytes(label), seed), length)
+    p_sha1 = P_hash(sha, S2, concatArrays(stringToBytes(label), seed), length)
+
+    #XOR the output values and return the result
+    for x in range(length):
+        p_md5[x] ^= p_sha1[x]
+    return p_md5
+
+
+def PRF_SSL(secret, seed, length):
+    secretStr = bytesToString(secret)
+    seedStr = bytesToString(seed)
+    bytes = createByteArrayZeros(length)
+    index = 0
+    for x in range(26):
+        A = chr(ord('A')+x) * (x+1) # 'A', 'BB', 'CCC', etc..
+        input = secretStr + sha.sha(A + secretStr + seedStr).digest()
+        output = md5.md5(input).digest()
+        for c in output:
+            if index >= length:
+                return bytes
+            bytes[index] = ord(c)
+            index += 1
+    return bytes
+
+def makeX(salt, username, password):
+    if len(username)>=256:
+        raise ValueError("username too long")
+    if len(salt)>=256:
+        raise ValueError("salt too long")
+    return stringToNumber(sha.sha(salt + sha.sha(username + ":" + password)\
+           .digest()).digest())
+
+#This function is used by VerifierDB.makeVerifier
+def makeVerifier(username, password, bits):
+    bitsIndex = {1024:0, 1536:1, 2048:2, 3072:3, 4096:4, 6144:5, 8192:6}[bits]
+    g,N = goodGroupParameters[bitsIndex]
+    salt = bytesToString(getRandomBytes(16))
+    x = makeX(salt, username, password)
+    verifier = powMod(g, x, N)
+    return N, g, salt, verifier
+
+def PAD(n, x):
+    nLength = len(numberToString(n))
+    s = numberToString(x)
+    if len(s) < nLength:
+        s = ("\0" * (nLength-len(s))) + s
+    return s
+
+def makeU(N, A, B):
+  return stringToNumber(sha.sha(PAD(N, A) + PAD(N, B)).digest())
+
+def makeK(N, g):
+  return stringToNumber(sha.sha(numberToString(N) + PAD(N, g)).digest())
+
+
+"""
+MAC_SSL
+Modified from Python HMAC by Trevor
+"""
+
+class MAC_SSL:
+    """MAC_SSL class.
+
+    This supports the API for Cryptographic Hash Functions (PEP 247).
+    """
+
+    def __init__(self, key, msg = None, digestmod = None):
+        """Create a new MAC_SSL object.
+
+        key:       key for the keyed hash object.
+        msg:       Initial input for the hash, if provided.
+        digestmod: A module supporting PEP 247. Defaults to the md5 module.
+        """
+        if digestmod is None:
+            import md5
+            digestmod = md5
+
+        if key == None: #TREVNEW - for faster copying
+            return      #TREVNEW
+
+        self.digestmod = digestmod
+        self.outer = digestmod.new()
+        self.inner = digestmod.new()
+        self.digest_size = digestmod.digest_size
+
+        ipad = "\x36" * 40
+        opad = "\x5C" * 40
+
+        self.inner.update(key)
+        self.inner.update(ipad)
+        self.outer.update(key)
+        self.outer.update(opad)
+        if msg is not None:
+            self.update(msg)
+
+
+    def update(self, msg):
+        """Update this hashing object with the string msg.
+        """
+        self.inner.update(msg)
+
+    def copy(self):
+        """Return a separate copy of this hashing object.
+
+        An update to this copy won't affect the original object.
+        """
+        other = MAC_SSL(None) #TREVNEW - for faster copying
+        other.digest_size = self.digest_size #TREVNEW
+        other.digestmod = self.digestmod
+        other.inner = self.inner.copy()
+        other.outer = self.outer.copy()
+        return other
+
+    def digest(self):
+        """Return the hash value of this hashing object.
+
+        This returns a string containing 8-bit data.  The object is
+        not altered in any way by this function; you can continue
+        updating the object after calling this function.
+        """
+        h = self.outer.copy()
+        h.update(self.inner.digest())
+        return h.digest()
+
+    def hexdigest(self):
+        """Like digest(), but returns a string of hexadecimal digits instead.
+        """
+        return "".join([hex(ord(x))[2:].zfill(2)
+                        for x in tuple(self.digest())])

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/messages.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/messages.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,561 @@
+"""Classes representing TLS messages."""
+
+from utils.compat import *
+from utils.cryptomath import *
+from errors import *
+from utils.codec import *
+from constants import *
+from X509 import X509
+from X509CertChain import X509CertChain
+
+import sha
+import md5
+
+class RecordHeader3:
+    def __init__(self):
+        self.type = 0
+        self.version = (0,0)
+        self.length = 0
+        self.ssl2 = False
+
+    def create(self, version, type, length):
+        self.type = type
+        self.version = version
+        self.length = length
+        return self
+
+    def write(self):
+        w = Writer(5)
+        w.add(self.type, 1)
+        w.add(self.version[0], 1)
+        w.add(self.version[1], 1)
+        w.add(self.length, 2)
+        return w.bytes
+
+    def parse(self, p):
+        self.type = p.get(1)
+        self.version = (p.get(1), p.get(1))
+        self.length = p.get(2)
+        self.ssl2 = False
+        return self
+
+class RecordHeader2:
+    def __init__(self):
+        self.type = 0
+        self.version = (0,0)
+        self.length = 0
+        self.ssl2 = True
+
+    def parse(self, p):
+        if p.get(1)!=128:
+            raise SyntaxError()
+        self.type = ContentType.handshake
+        self.version = (2,0)
+        #We don't support 2-byte-length-headers; could be a problem
+        self.length = p.get(1)
+        return self
+
+
+class Msg:
+    def preWrite(self, trial):
+        if trial:
+            w = Writer()
+        else:
+            length = self.write(True)
+            w = Writer(length)
+        return w
+
+    def postWrite(self, w, trial):
+        if trial:
+            return w.index
+        else:
+            return w.bytes
+
+class Alert(Msg):
+    def __init__(self):
+        self.contentType = ContentType.alert
+        self.level = 0
+        self.description = 0
+
+    def create(self, description, level=AlertLevel.fatal):
+        self.level = level
+        self.description = description
+        return self
+
+    def parse(self, p):
+        p.setLengthCheck(2)
+        self.level = p.get(1)
+        self.description = p.get(1)
+        p.stopLengthCheck()
+        return self
+
+    def write(self):
+        w = Writer(2)
+        w.add(self.level, 1)
+        w.add(self.description, 1)
+        return w.bytes
+
+
+class HandshakeMsg(Msg):
+    def preWrite(self, handshakeType, trial):
+        if trial:
+            w = Writer()
+            w.add(handshakeType, 1)
+            w.add(0, 3)
+        else:
+            length = self.write(True)
+            w = Writer(length)
+            w.add(handshakeType, 1)
+            w.add(length-4, 3)
+        return w
+
+
+class ClientHello(HandshakeMsg):
+    def __init__(self, ssl2=False):
+        self.contentType = ContentType.handshake
+        self.ssl2 = ssl2
+        self.client_version = (0,0)
+        self.random = createByteArrayZeros(32)
+        self.session_id = createByteArraySequence([])
+        self.cipher_suites = []         # a list of 16-bit values
+        self.certificate_types = [CertificateType.x509]
+        self.compression_methods = []   # a list of 8-bit values
+        self.srp_username = None        # a string
+
+    def create(self, version, random, session_id, cipher_suites,
+               certificate_types=None, srp_username=None):
+        self.client_version = version
+        self.random = random
+        self.session_id = session_id
+        self.cipher_suites = cipher_suites
+        self.certificate_types = certificate_types
+        self.compression_methods = [0]
+        self.srp_username = srp_username
+        return self
+
+    def parse(self, p):
+        if self.ssl2:
+            self.client_version = (p.get(1), p.get(1))
+            cipherSpecsLength = p.get(2)
+            sessionIDLength = p.get(2)
+            randomLength = p.get(2)
+            self.cipher_suites = p.getFixList(3, int(cipherSpecsLength/3))
+            self.session_id = p.getFixBytes(sessionIDLength)
+            self.random = p.getFixBytes(randomLength)
+            if len(self.random) < 32:
+                zeroBytes = 32-len(self.random)
+                self.random = createByteArrayZeros(zeroBytes) + self.random
+            self.compression_methods = [0]#Fake this value
+
+            #We're not doing a stopLengthCheck() for SSLv2, oh well..
+        else:
+            p.startLengthCheck(3)
+            self.client_version = (p.get(1), p.get(1))
+            self.random = p.getFixBytes(32)
+            self.session_id = p.getVarBytes(1)
+            self.cipher_suites = p.getVarList(2, 2)
+            self.compression_methods = p.getVarList(1, 1)
+            if not p.atLengthCheck():
+                totalExtLength = p.get(2)
+                soFar = 0
+                while soFar != totalExtLength:
+                    extType = p.get(2)
+                    extLength = p.get(2)
+                    if extType == 6:
+                        self.srp_username = bytesToString(p.getVarBytes(1))
+                    elif extType == 7:
+                        self.certificate_types = p.getVarList(1, 1)
+                    else:
+                        p.getFixBytes(extLength)
+                    soFar += 4 + extLength
+            p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.client_hello, trial)
+        w.add(self.client_version[0], 1)
+        w.add(self.client_version[1], 1)
+        w.addFixSeq(self.random, 1)
+        w.addVarSeq(self.session_id, 1, 1)
+        w.addVarSeq(self.cipher_suites, 2, 2)
+        w.addVarSeq(self.compression_methods, 1, 1)
+
+        extLength = 0
+        if self.certificate_types and self.certificate_types != \
+                [CertificateType.x509]:
+            extLength += 5 + len(self.certificate_types)
+        if self.srp_username:
+            extLength += 5 + len(self.srp_username)
+        if extLength > 0:
+            w.add(extLength, 2)
+
+        if self.certificate_types and self.certificate_types != \
+                [CertificateType.x509]:
+            w.add(7, 2)
+            w.add(len(self.certificate_types)+1, 2)
+            w.addVarSeq(self.certificate_types, 1, 1)
+        if self.srp_username:
+            w.add(6, 2)
+            w.add(len(self.srp_username)+1, 2)
+            w.addVarSeq(stringToBytes(self.srp_username), 1, 1)
+
+        return HandshakeMsg.postWrite(self, w, trial)
+
+
+class ServerHello(HandshakeMsg):
+    def __init__(self):
+        self.contentType = ContentType.handshake
+        self.server_version = (0,0)
+        self.random = createByteArrayZeros(32)
+        self.session_id = createByteArraySequence([])
+        self.cipher_suite = 0
+        self.certificate_type = CertificateType.x509
+        self.compression_method = 0
+
+    def create(self, version, random, session_id, cipher_suite,
+               certificate_type):
+        self.server_version = version
+        self.random = random
+        self.session_id = session_id
+        self.cipher_suite = cipher_suite
+        self.certificate_type = certificate_type
+        self.compression_method = 0
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        self.server_version = (p.get(1), p.get(1))
+        self.random = p.getFixBytes(32)
+        self.session_id = p.getVarBytes(1)
+        self.cipher_suite = p.get(2)
+        self.compression_method = p.get(1)
+        if not p.atLengthCheck():
+            totalExtLength = p.get(2)
+            soFar = 0
+            while soFar != totalExtLength:
+                extType = p.get(2)
+                extLength = p.get(2)
+                if extType == 7:
+                    self.certificate_type = p.get(1)
+                else:
+                    p.getFixBytes(extLength)
+                soFar += 4 + extLength
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.server_hello, trial)
+        w.add(self.server_version[0], 1)
+        w.add(self.server_version[1], 1)
+        w.addFixSeq(self.random, 1)
+        w.addVarSeq(self.session_id, 1, 1)
+        w.add(self.cipher_suite, 2)
+        w.add(self.compression_method, 1)
+
+        extLength = 0
+        if self.certificate_type and self.certificate_type != \
+                CertificateType.x509:
+            extLength += 5
+
+        if extLength != 0:
+            w.add(extLength, 2)
+
+        if self.certificate_type and self.certificate_type != \
+                CertificateType.x509:
+            w.add(7, 2)
+            w.add(1, 2)
+            w.add(self.certificate_type, 1)
+
+        return HandshakeMsg.postWrite(self, w, trial)
+
+class Certificate(HandshakeMsg):
+    def __init__(self, certificateType):
+        self.certificateType = certificateType
+        self.contentType = ContentType.handshake
+        self.certChain = None
+
+    def create(self, certChain):
+        self.certChain = certChain
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        if self.certificateType == CertificateType.x509:
+            chainLength = p.get(3)
+            index = 0
+            certificate_list = []
+            while index != chainLength:
+                certBytes = p.getVarBytes(3)
+                x509 = X509()
+                x509.parseBinary(certBytes)
+                certificate_list.append(x509)
+                index += len(certBytes)+3
+            if certificate_list:
+                self.certChain = X509CertChain(certificate_list)
+        elif self.certificateType == CertificateType.cryptoID:
+            s = bytesToString(p.getVarBytes(2))
+            if s:
+                try:
+                    import cryptoIDlib.CertChain
+                except ImportError:
+                    raise SyntaxError(\
+                    "cryptoID cert chain received, cryptoIDlib not present")
+                self.certChain = cryptoIDlib.CertChain.CertChain().parse(s)
+        else:
+            raise AssertionError()
+
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.certificate, trial)
+        if self.certificateType == CertificateType.x509:
+            chainLength = 0
+            if self.certChain:
+                certificate_list = self.certChain.x509List
+            else:
+                certificate_list = []
+            #determine length
+            for cert in certificate_list:
+                bytes = cert.writeBytes()
+                chainLength += len(bytes)+3
+            #add bytes
+            w.add(chainLength, 3)
+            for cert in certificate_list:
+                bytes = cert.writeBytes()
+                w.addVarSeq(bytes, 1, 3)
+        elif self.certificateType == CertificateType.cryptoID:
+            if self.certChain:
+                bytes = stringToBytes(self.certChain.write())
+            else:
+                bytes = createByteArraySequence([])
+            w.addVarSeq(bytes, 1, 2)
+        else:
+            raise AssertionError()
+        return HandshakeMsg.postWrite(self, w, trial)
+
+class CertificateRequest(HandshakeMsg):
+    def __init__(self):
+        self.contentType = ContentType.handshake
+        self.certificate_types = []
+        #treat as opaque bytes for now
+        self.certificate_authorities = createByteArraySequence([])
+
+    def create(self, certificate_types, certificate_authorities):
+        self.certificate_types = certificate_types
+        self.certificate_authorities = certificate_authorities
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        self.certificate_types = p.getVarList(1, 1)
+        self.certificate_authorities = p.getVarBytes(2)
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.certificate_request,
+                                  trial)
+        w.addVarSeq(self.certificate_types, 1, 1)
+        w.addVarSeq(self.certificate_authorities, 1, 2)
+        return HandshakeMsg.postWrite(self, w, trial)
+
+class ServerKeyExchange(HandshakeMsg):
+    def __init__(self, cipherSuite):
+        self.cipherSuite = cipherSuite
+        self.contentType = ContentType.handshake
+        self.srp_N = 0L
+        self.srp_g = 0L
+        self.srp_s = createByteArraySequence([])
+        self.srp_B = 0L
+        self.signature = createByteArraySequence([])
+
+    def createSRP(self, srp_N, srp_g, srp_s, srp_B):
+        self.srp_N = srp_N
+        self.srp_g = srp_g
+        self.srp_s = srp_s
+        self.srp_B = srp_B
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        self.srp_N = bytesToNumber(p.getVarBytes(2))
+        self.srp_g = bytesToNumber(p.getVarBytes(2))
+        self.srp_s = p.getVarBytes(1)
+        self.srp_B = bytesToNumber(p.getVarBytes(2))
+        if self.cipherSuite in CipherSuite.srpRsaSuites:
+            self.signature = p.getVarBytes(2)
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.server_key_exchange,
+                                  trial)
+        w.addVarSeq(numberToBytes(self.srp_N), 1, 2)
+        w.addVarSeq(numberToBytes(self.srp_g), 1, 2)
+        w.addVarSeq(self.srp_s, 1, 1)
+        w.addVarSeq(numberToBytes(self.srp_B), 1, 2)
+        if self.cipherSuite in CipherSuite.srpRsaSuites:
+            w.addVarSeq(self.signature, 1, 2)
+        return HandshakeMsg.postWrite(self, w, trial)
+
+    def hash(self, clientRandom, serverRandom):
+        oldCipherSuite = self.cipherSuite
+        self.cipherSuite = None
+        try:
+            bytes = clientRandom + serverRandom + self.write()[4:]
+            s = bytesToString(bytes)
+            return stringToBytes(md5.md5(s).digest() + sha.sha(s).digest())
+        finally:
+            self.cipherSuite = oldCipherSuite
+
+class ServerHelloDone(HandshakeMsg):
+    def __init__(self):
+        self.contentType = ContentType.handshake
+
+    def create(self):
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.server_hello_done, trial)
+        return HandshakeMsg.postWrite(self, w, trial)
+
+class ClientKeyExchange(HandshakeMsg):
+    def __init__(self, cipherSuite, version=None):
+        self.cipherSuite = cipherSuite
+        self.version = version
+        self.contentType = ContentType.handshake
+        self.srp_A = 0
+        self.encryptedPreMasterSecret = createByteArraySequence([])
+
+    def createSRP(self, srp_A):
+        self.srp_A = srp_A
+        return self
+
+    def createRSA(self, encryptedPreMasterSecret):
+        self.encryptedPreMasterSecret = encryptedPreMasterSecret
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        if self.cipherSuite in CipherSuite.srpSuites + \
+                               CipherSuite.srpRsaSuites:
+            self.srp_A = bytesToNumber(p.getVarBytes(2))
+        elif self.cipherSuite in CipherSuite.rsaSuites:
+            if self.version in ((3,1), (3,2)):
+                self.encryptedPreMasterSecret = p.getVarBytes(2)
+            elif self.version == (3,0):
+                self.encryptedPreMasterSecret = \
+                    p.getFixBytes(len(p.bytes)-p.index)
+            else:
+                raise AssertionError()
+        else:
+            raise AssertionError()
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.client_key_exchange,
+                                  trial)
+        if self.cipherSuite in CipherSuite.srpSuites + \
+                               CipherSuite.srpRsaSuites:
+            w.addVarSeq(numberToBytes(self.srp_A), 1, 2)
+        elif self.cipherSuite in CipherSuite.rsaSuites:
+            if self.version in ((3,1), (3,2)):
+                w.addVarSeq(self.encryptedPreMasterSecret, 1, 2)
+            elif self.version == (3,0):
+                w.addFixSeq(self.encryptedPreMasterSecret, 1)
+            else:
+                raise AssertionError()
+        else:
+            raise AssertionError()
+        return HandshakeMsg.postWrite(self, w, trial)
+
+class CertificateVerify(HandshakeMsg):
+    def __init__(self):
+        self.contentType = ContentType.handshake
+        self.signature = createByteArraySequence([])
+
+    def create(self, signature):
+        self.signature = signature
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        self.signature = p.getVarBytes(2)
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.certificate_verify,
+                                  trial)
+        w.addVarSeq(self.signature, 1, 2)
+        return HandshakeMsg.postWrite(self, w, trial)
+
+class ChangeCipherSpec(Msg):
+    def __init__(self):
+        self.contentType = ContentType.change_cipher_spec
+        self.type = 1
+
+    def create(self):
+        self.type = 1
+        return self
+
+    def parse(self, p):
+        p.setLengthCheck(1)
+        self.type = p.get(1)
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = Msg.preWrite(self, trial)
+        w.add(self.type,1)
+        return Msg.postWrite(self, w, trial)
+
+
+class Finished(HandshakeMsg):
+    def __init__(self, version):
+        self.contentType = ContentType.handshake
+        self.version = version
+        self.verify_data = createByteArraySequence([])
+
+    def create(self, verify_data):
+        self.verify_data = verify_data
+        return self
+
+    def parse(self, p):
+        p.startLengthCheck(3)
+        if self.version == (3,0):
+            self.verify_data = p.getFixBytes(36)
+        elif self.version in ((3,1), (3,2)):
+            self.verify_data = p.getFixBytes(12)
+        else:
+            raise AssertionError()
+        p.stopLengthCheck()
+        return self
+
+    def write(self, trial=False):
+        w = HandshakeMsg.preWrite(self, HandshakeType.finished, trial)
+        w.addFixSeq(self.verify_data, 1)
+        return HandshakeMsg.postWrite(self, w, trial)
+
+class ApplicationData(Msg):
+    def __init__(self):
+        self.contentType = ContentType.application_data
+        self.bytes = createByteArraySequence([])
+
+    def create(self, bytes):
+        self.bytes = bytes
+        return self
+
+    def parse(self, p):
+        self.bytes = p.bytes
+        return self
+
+    def write(self):
+        return self.bytes
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/AES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/AES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,31 @@
+"""Abstract class for AES."""
+
+class AES:
+    def __init__(self, key, mode, IV, implementation):
+        if len(key) not in (16, 24, 32):
+            raise AssertionError()
+        if mode != 2:
+            raise AssertionError()
+        if len(IV) != 16:
+            raise AssertionError()
+        self.isBlockCipher = True
+        self.block_size = 16
+        self.implementation = implementation
+        if len(key)==16:
+            self.name = "aes128"
+        elif len(key)==24:
+            self.name = "aes192"
+        elif len(key)==32:
+            self.name = "aes256"
+        else:
+            raise AssertionError()
+
+    #CBC-Mode encryption, returns ciphertext
+    #WARNING: *MAY* modify the input as well
+    def encrypt(self, plaintext):
+        assert(len(plaintext) % 16 == 0)
+
+    #CBC-Mode decryption, returns plaintext
+    #WARNING: *MAY* modify the input as well
+    def decrypt(self, ciphertext):
+        assert(len(ciphertext) % 16 == 0)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/ASN1Parser.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/ASN1Parser.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,34 @@
+"""Class for parsing ASN.1"""
+from compat import *
+from codec import *
+
+#Takes a byte array which has a DER TLV field at its head
+class ASN1Parser:
+    def __init__(self, bytes):
+        p = Parser(bytes)
+        p.get(1) #skip Type
+
+        #Get Length
+        self.length = self._getASN1Length(p)
+
+        #Get Value
+        self.value = p.getFixBytes(self.length)
+
+    #Assuming this is a sequence...
+    def getChild(self, which):
+        p = Parser(self.value)
+        for x in range(which+1):
+            markIndex = p.index
+            p.get(1) #skip Type
+            length = self._getASN1Length(p)
+            p.getFixBytes(length)
+        return ASN1Parser(p.bytes[markIndex : p.index])
+
+    #Decode the ASN.1 DER length field
+    def _getASN1Length(self, p):
+        firstLength = p.get(1)
+        if firstLength<=127:
+            return firstLength
+        else:
+            lengthLength = firstLength & 0x7F
+            return p.get(lengthLength)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_AES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_AES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,34 @@
+"""Cryptlib AES implementation."""
+
+from cryptomath import *
+from AES import *
+
+if cryptlibpyLoaded:
+
+    def new(key, mode, IV):
+        return Cryptlib_AES(key, mode, IV)
+
+    class Cryptlib_AES(AES):
+
+        def __init__(self, key, mode, IV):
+            AES.__init__(self, key, mode, IV, "cryptlib")
+            self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_AES)
+            cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_CBC)
+            cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key))
+            cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key)
+            cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_IV, IV)
+
+        def __del__(self):
+             cryptlib_py.cryptDestroyContext(self.context)
+
+        def encrypt(self, plaintext):
+            AES.encrypt(self, plaintext)
+            bytes = stringToBytes(plaintext)
+            cryptlib_py.cryptEncrypt(self.context, bytes)
+            return bytesToString(bytes)
+
+        def decrypt(self, ciphertext):
+            AES.decrypt(self, ciphertext)
+            bytes = stringToBytes(ciphertext)
+            cryptlib_py.cryptDecrypt(self.context, bytes)
+            return bytesToString(bytes)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_RC4.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_RC4.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,28 @@
+"""Cryptlib RC4 implementation."""
+
+from cryptomath import *
+from RC4 import RC4
+
+if cryptlibpyLoaded:
+
+    def new(key):
+        return Cryptlib_RC4(key)
+
+    class Cryptlib_RC4(RC4):
+
+        def __init__(self, key):
+            RC4.__init__(self, key, "cryptlib")
+            self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_RC4)
+            cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key))
+            cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key)
+
+        def __del__(self):
+             cryptlib_py.cryptDestroyContext(self.context)
+
+        def encrypt(self, plaintext):
+            bytes = stringToBytes(plaintext)
+            cryptlib_py.cryptEncrypt(self.context, bytes)
+            return bytesToString(bytes)
+
+        def decrypt(self, ciphertext):
+            return self.encrypt(ciphertext)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_TripleDES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Cryptlib_TripleDES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,35 @@
+"""Cryptlib 3DES implementation."""
+
+from cryptomath import *
+
+from TripleDES import *
+
+if cryptlibpyLoaded:
+
+    def new(key, mode, IV):
+        return Cryptlib_TripleDES(key, mode, IV)
+
+    class Cryptlib_TripleDES(TripleDES):
+
+        def __init__(self, key, mode, IV):
+            TripleDES.__init__(self, key, mode, IV, "cryptlib")
+            self.context = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED, cryptlib_py.CRYPT_ALGO_3DES)
+            cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_MODE, cryptlib_py.CRYPT_MODE_CBC)
+            cryptlib_py.cryptSetAttribute(self.context, cryptlib_py.CRYPT_CTXINFO_KEYSIZE, len(key))
+            cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_KEY, key)
+            cryptlib_py.cryptSetAttributeString(self.context, cryptlib_py.CRYPT_CTXINFO_IV, IV)
+
+        def __del__(self):
+             cryptlib_py.cryptDestroyContext(self.context)
+
+        def encrypt(self, plaintext):
+            TripleDES.encrypt(self, plaintext)
+            bytes = stringToBytes(plaintext)
+            cryptlib_py.cryptEncrypt(self.context, bytes)
+            return bytesToString(bytes)
+
+        def decrypt(self, ciphertext):
+            TripleDES.decrypt(self, ciphertext)
+            bytes = stringToBytes(ciphertext)
+            cryptlib_py.cryptDecrypt(self.context, bytes)
+            return bytesToString(bytes)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_AES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_AES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,49 @@
+"""OpenSSL/M2Crypto AES implementation."""
+
+from cryptomath import *
+from AES import *
+
+if m2cryptoLoaded:
+
+    def new(key, mode, IV):
+        return OpenSSL_AES(key, mode, IV)
+
+    class OpenSSL_AES(AES):
+
+        def __init__(self, key, mode, IV):
+            AES.__init__(self, key, mode, IV, "openssl")
+            self.key = key
+            self.IV = IV
+
+        def _createContext(self, encrypt):
+            context = m2.cipher_ctx_new()
+            if len(self.key)==16:
+                cipherType = m2.aes_128_cbc()
+            if len(self.key)==24:
+                cipherType = m2.aes_192_cbc()
+            if len(self.key)==32:
+                cipherType = m2.aes_256_cbc()
+            m2.cipher_init(context, cipherType, self.key, self.IV, encrypt)
+            return context
+
+        def encrypt(self, plaintext):
+            AES.encrypt(self, plaintext)
+            context = self._createContext(1)
+            ciphertext = m2.cipher_update(context, plaintext)
+            m2.cipher_ctx_free(context)
+            self.IV = ciphertext[-self.block_size:]
+            return ciphertext
+
+        def decrypt(self, ciphertext):
+            AES.decrypt(self, ciphertext)
+            context = self._createContext(0)
+            #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in.
+            #To work around this, we append sixteen zeros to the string, below:
+            plaintext = m2.cipher_update(context, ciphertext+('\0'*16))
+
+            #If this bug is ever fixed, then plaintext will end up having a garbage
+            #plaintext block on the end.  That's okay - the below code will discard it.
+            plaintext = plaintext[:len(ciphertext)]
+            m2.cipher_ctx_free(context)
+            self.IV = ciphertext[-self.block_size:]
+            return plaintext

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_RC4.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_RC4.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,25 @@
+"""OpenSSL/M2Crypto RC4 implementation."""
+
+from cryptomath import *
+from RC4 import RC4
+
+if m2cryptoLoaded:
+
+    def new(key):
+        return OpenSSL_RC4(key)
+
+    class OpenSSL_RC4(RC4):
+
+        def __init__(self, key):
+            RC4.__init__(self, key, "openssl")
+            self.rc4 = m2.rc4_new()
+            m2.rc4_set_key(self.rc4, key)
+
+        def __del__(self):
+            m2.rc4_free(self.rc4)
+
+        def encrypt(self, plaintext):
+            return m2.rc4_update(self.rc4, plaintext)
+
+        def decrypt(self, ciphertext):
+            return self.encrypt(ciphertext)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_RSAKey.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_RSAKey.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,148 @@
+"""OpenSSL/M2Crypto RSA implementation."""
+
+from cryptomath import *
+
+from RSAKey import *
+from Python_RSAKey import Python_RSAKey
+
+#copied from M2Crypto.util.py, so when we load the local copy of m2
+#we can still use it
+def password_callback(v, prompt1='Enter private key passphrase:',
+                           prompt2='Verify passphrase:'):
+    from getpass import getpass
+    while 1:
+        try:
+            p1=getpass(prompt1)
+            if v:
+                p2=getpass(prompt2)
+                if p1==p2:
+                    break
+            else:
+                break
+        except KeyboardInterrupt:
+            return None
+    return p1
+
+
+if m2cryptoLoaded:
+    class OpenSSL_RSAKey(RSAKey):
+        def __init__(self, n=0, e=0):
+            self.rsa = None
+            self._hasPrivateKey = False
+            if (n and not e) or (e and not n):
+                raise AssertionError()
+            if n and e:
+                self.rsa = m2.rsa_new()
+                m2.rsa_set_n(self.rsa, numberToMPI(n))
+                m2.rsa_set_e(self.rsa, numberToMPI(e))
+
+        def __del__(self):
+            if self.rsa:
+                m2.rsa_free(self.rsa)
+
+        def __getattr__(self, name):
+            if name == 'e':
+                if not self.rsa:
+                    return 0
+                return mpiToNumber(m2.rsa_get_e(self.rsa))
+            elif name == 'n':
+                if not self.rsa:
+                    return 0
+                return mpiToNumber(m2.rsa_get_n(self.rsa))
+            else:
+                raise AttributeError
+
+        def hasPrivateKey(self):
+            return self._hasPrivateKey
+
+        def hash(self):
+            return Python_RSAKey(self.n, self.e).hash()
+
+        def _rawPrivateKeyOp(self, m):
+            s = numberToString(m)
+            byteLength = numBytes(self.n)
+            if len(s)== byteLength:
+                pass
+            elif len(s) == byteLength-1:
+                s = '\0' + s
+            else:
+                raise AssertionError()
+            c = stringToNumber(m2.rsa_private_encrypt(self.rsa, s,
+                                                      m2.no_padding))
+            return c
+
+        def _rawPublicKeyOp(self, c):
+            s = numberToString(c)
+            byteLength = numBytes(self.n)
+            if len(s)== byteLength:
+                pass
+            elif len(s) == byteLength-1:
+                s = '\0' + s
+            else:
+                raise AssertionError()
+            m = stringToNumber(m2.rsa_public_decrypt(self.rsa, s,
+                                                     m2.no_padding))
+            return m
+
+        def acceptsPassword(self): return True
+
+        def write(self, password=None):
+            bio = m2.bio_new(m2.bio_s_mem())
+            if self._hasPrivateKey:
+                if password:
+                    def f(v): return password
+                    m2.rsa_write_key(self.rsa, bio, m2.des_ede_cbc(), f)
+                else:
+                    def f(): pass
+                    m2.rsa_write_key_no_cipher(self.rsa, bio, f)
+            else:
+                if password:
+                    raise AssertionError()
+                m2.rsa_write_pub_key(self.rsa, bio)
+            s = m2.bio_read(bio, m2.bio_ctrl_pending(bio))
+            m2.bio_free(bio)
+            return s
+
+        def writeXMLPublicKey(self, indent=''):
+            return Python_RSAKey(self.n, self.e).write(indent)
+
+        def generate(bits):
+            key = OpenSSL_RSAKey()
+            def f():pass
+            key.rsa = m2.rsa_generate_key(bits, 3, f)
+            key._hasPrivateKey = True
+            return key
+        generate = staticmethod(generate)
+
+        def parse(s, passwordCallback=None):
+            if s.startswith("-----BEGIN "):
+                if passwordCallback==None:
+                    callback = password_callback
+                else:
+                    def f(v, prompt1=None, prompt2=None):
+                        return passwordCallback()
+                    callback = f
+                bio = m2.bio_new(m2.bio_s_mem())
+                try:
+                    m2.bio_write(bio, s)
+                    key = OpenSSL_RSAKey()
+                    if s.startswith("-----BEGIN RSA PRIVATE KEY-----"):
+                        def f():pass
+                        key.rsa = m2.rsa_read_key(bio, callback)
+                        if key.rsa == None:
+                            raise SyntaxError()
+                        key._hasPrivateKey = True
+                    elif s.startswith("-----BEGIN PUBLIC KEY-----"):
+                        key.rsa = m2.rsa_read_pub_key(bio)
+                        if key.rsa == None:
+                            raise SyntaxError()
+                        key._hasPrivateKey = False
+                    else:
+                        raise SyntaxError()
+                    return key
+                finally:
+                    m2.bio_free(bio)
+            else:
+                raise SyntaxError()
+
+        parse = staticmethod(parse)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_TripleDES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/OpenSSL_TripleDES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,44 @@
+"""OpenSSL/M2Crypto 3DES implementation."""
+
+from cryptomath import *
+from TripleDES import *
+
+if m2cryptoLoaded:
+
+    def new(key, mode, IV):
+        return OpenSSL_TripleDES(key, mode, IV)
+
+    class OpenSSL_TripleDES(TripleDES):
+
+        def __init__(self, key, mode, IV):
+            TripleDES.__init__(self, key, mode, IV, "openssl")
+            self.key = key
+            self.IV = IV
+
+        def _createContext(self, encrypt):
+            context = m2.cipher_ctx_new()
+            cipherType = m2.des_ede3_cbc()
+            m2.cipher_init(context, cipherType, self.key, self.IV, encrypt)
+            return context
+
+        def encrypt(self, plaintext):
+            TripleDES.encrypt(self, plaintext)
+            context = self._createContext(1)
+            ciphertext = m2.cipher_update(context, plaintext)
+            m2.cipher_ctx_free(context)
+            self.IV = ciphertext[-self.block_size:]
+            return ciphertext
+
+        def decrypt(self, ciphertext):
+            TripleDES.decrypt(self, ciphertext)
+            context = self._createContext(0)
+            #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in.
+            #To work around this, we append sixteen zeros to the string, below:
+            plaintext = m2.cipher_update(context, ciphertext+('\0'*16))
+
+            #If this bug is ever fixed, then plaintext will end up having a garbage
+            #plaintext block on the end.  That's okay - the below code will ignore it.
+            plaintext = plaintext[:len(ciphertext)]
+            m2.cipher_ctx_free(context)
+            self.IV = ciphertext[-self.block_size:]
+            return plaintext
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_AES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_AES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,22 @@
+"""PyCrypto AES implementation."""
+
+from cryptomath import *
+from AES import *
+
+if pycryptoLoaded:
+    import Crypto.Cipher.AES
+
+    def new(key, mode, IV):
+        return PyCrypto_AES(key, mode, IV)
+
+    class PyCrypto_AES(AES):
+
+        def __init__(self, key, mode, IV):
+            AES.__init__(self, key, mode, IV, "pycrypto")
+            self.context = Crypto.Cipher.AES.new(key, mode, IV)
+
+        def encrypt(self, plaintext):
+            return self.context.encrypt(plaintext)
+
+        def decrypt(self, ciphertext):
+            return self.context.decrypt(ciphertext)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_RC4.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_RC4.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,22 @@
+"""PyCrypto RC4 implementation."""
+
+from cryptomath import *
+from RC4 import *
+
+if pycryptoLoaded:
+    import Crypto.Cipher.ARC4
+
+    def new(key):
+        return PyCrypto_RC4(key)
+
+    class PyCrypto_RC4(RC4):
+
+        def __init__(self, key):
+            RC4.__init__(self, key, "pycrypto")
+            self.context = Crypto.Cipher.ARC4.new(key)
+
+        def encrypt(self, plaintext):
+            return self.context.encrypt(plaintext)
+
+        def decrypt(self, ciphertext):
+            return self.context.decrypt(ciphertext)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_RSAKey.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_RSAKey.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,61 @@
+"""PyCrypto RSA implementation."""
+
+from cryptomath import *
+
+from RSAKey import *
+from Python_RSAKey import Python_RSAKey
+
+if pycryptoLoaded:
+
+    from Crypto.PublicKey import RSA
+
+    class PyCrypto_RSAKey(RSAKey):
+        def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0):
+            if not d:
+                self.rsa = RSA.construct( (n, e) )
+            else:
+                self.rsa = RSA.construct( (n, e, d, p, q) )
+
+        def __getattr__(self, name):
+            return getattr(self.rsa, name)
+
+        def hasPrivateKey(self):
+            return self.rsa.has_private()
+
+        def hash(self):
+            return Python_RSAKey(self.n, self.e).hash()
+
+        def _rawPrivateKeyOp(self, m):
+            s = numberToString(m)
+            byteLength = numBytes(self.n)
+            if len(s)== byteLength:
+                pass
+            elif len(s) == byteLength-1:
+                s = '\0' + s
+            else:
+                raise AssertionError()
+            c = stringToNumber(self.rsa.decrypt((s,)))
+            return c
+
+        def _rawPublicKeyOp(self, c):
+            s = numberToString(c)
+            byteLength = numBytes(self.n)
+            if len(s)== byteLength:
+                pass
+            elif len(s) == byteLength-1:
+                s = '\0' + s
+            else:
+                raise AssertionError()
+            m = stringToNumber(self.rsa.encrypt(s, None)[0])
+            return m
+
+        def writeXMLPublicKey(self, indent=''):
+            return Python_RSAKey(self.n, self.e).write(indent)
+
+        def generate(bits):
+            key = PyCrypto_RSAKey()
+            def f(numBytes):
+                return bytesToString(getRandomBytes(numBytes))
+            key.rsa = RSA.generate(bits, f)
+            return key
+        generate = staticmethod(generate)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_TripleDES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/PyCrypto_TripleDES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,22 @@
+"""PyCrypto 3DES implementation."""
+
+from cryptomath import *
+from TripleDES import *
+
+if pycryptoLoaded:
+    import Crypto.Cipher.DES3
+
+    def new(key, mode, IV):
+        return PyCrypto_TripleDES(key, mode, IV)
+
+    class PyCrypto_TripleDES(TripleDES):
+
+        def __init__(self, key, mode, IV):
+            TripleDES.__init__(self, key, mode, IV, "pycrypto")
+            self.context = Crypto.Cipher.DES3.new(key, mode, IV)
+
+        def encrypt(self, plaintext):
+            return self.context.encrypt(plaintext)
+
+        def decrypt(self, ciphertext):
+            return self.context.decrypt(ciphertext)
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_AES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_AES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,68 @@
+"""Pure-Python AES implementation."""
+
+from cryptomath import *
+
+from AES import *
+from rijndael import rijndael
+
+def new(key, mode, IV):
+    return Python_AES(key, mode, IV)
+
+class Python_AES(AES):
+    def __init__(self, key, mode, IV):
+        AES.__init__(self, key, mode, IV, "python")
+        self.rijndael = rijndael(key, 16)
+        self.IV = IV
+
+    def encrypt(self, plaintext):
+        AES.encrypt(self, plaintext)
+
+        plaintextBytes = stringToBytes(plaintext)
+        chainBytes = stringToBytes(self.IV)
+
+        #CBC Mode: For each block...
+        for x in range(len(plaintextBytes)/16):
+
+            #XOR with the chaining block
+            blockBytes = plaintextBytes[x*16 : (x*16)+16]
+            for y in range(16):
+                blockBytes[y] ^= chainBytes[y]
+            blockString = bytesToString(blockBytes)
+
+            #Encrypt it
+            encryptedBytes = stringToBytes(self.rijndael.encrypt(blockString))
+
+            #Overwrite the input with the output
+            for y in range(16):
+                plaintextBytes[(x*16)+y] = encryptedBytes[y]
+
+            #Set the next chaining block
+            chainBytes = encryptedBytes
+
+        self.IV = bytesToString(chainBytes)
+        return bytesToString(plaintextBytes)
+
+    def decrypt(self, ciphertext):
+        AES.decrypt(self, ciphertext)
+
+        ciphertextBytes = stringToBytes(ciphertext)
+        chainBytes = stringToBytes(self.IV)
+
+        #CBC Mode: For each block...
+        for x in range(len(ciphertextBytes)/16):
+
+            #Decrypt it
+            blockBytes = ciphertextBytes[x*16 : (x*16)+16]
+            blockString = bytesToString(blockBytes)
+            decryptedBytes = stringToBytes(self.rijndael.decrypt(blockString))
+
+            #XOR with the chaining block and overwrite the input with output
+            for y in range(16):
+                decryptedBytes[y] ^= chainBytes[y]
+                ciphertextBytes[(x*16)+y] = decryptedBytes[y]
+
+            #Set the next chaining block
+            chainBytes = blockBytes
+
+        self.IV = bytesToString(chainBytes)
+        return bytesToString(ciphertextBytes)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_RC4.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_RC4.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,39 @@
+"""Pure-Python RC4 implementation."""
+
+from RC4 import RC4
+from cryptomath import *
+
+def new(key):
+    return Python_RC4(key)
+
+class Python_RC4(RC4):
+    def __init__(self, key):
+        RC4.__init__(self, key, "python")
+        keyBytes = stringToBytes(key)
+        S = [i for i in range(256)]
+        j = 0
+        for i in range(256):
+            j = (j + S[i] + keyBytes[i % len(keyBytes)]) % 256
+            S[i], S[j] = S[j], S[i]
+
+        self.S = S
+        self.i = 0
+        self.j = 0
+
+    def encrypt(self, plaintext):
+        plaintextBytes = stringToBytes(plaintext)
+        S = self.S
+        i = self.i
+        j = self.j
+        for x in range(len(plaintextBytes)):
+            i = (i + 1) % 256
+            j = (j + S[i]) % 256
+            S[i], S[j] = S[j], S[i]
+            t = (S[i] + S[j]) % 256
+            plaintextBytes[x] ^= S[t]
+        self.i = i
+        self.j = j
+        return bytesToString(plaintextBytes)
+
+    def decrypt(self, ciphertext):
+        return self.encrypt(ciphertext)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_RSAKey.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/Python_RSAKey.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,209 @@
+"""Pure-Python RSA implementation."""
+
+from cryptomath import *
+import xmltools
+from ASN1Parser import ASN1Parser
+from RSAKey import *
+
+class Python_RSAKey(RSAKey):
+    def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0):
+        if (n and not e) or (e and not n):
+            raise AssertionError()
+        self.n = n
+        self.e = e
+        self.d = d
+        self.p = p
+        self.q = q
+        self.dP = dP
+        self.dQ = dQ
+        self.qInv = qInv
+        self.blinder = 0
+        self.unblinder = 0
+
+    def hasPrivateKey(self):
+        return self.d != 0
+
+    def hash(self):
+        s = self.writeXMLPublicKey('\t\t')
+        return hashAndBase64(s.strip())
+
+    def _rawPrivateKeyOp(self, m):
+        #Create blinding values, on the first pass:
+        if not self.blinder:
+            self.unblinder = getRandomNumber(2, self.n)
+            self.blinder = powMod(invMod(self.unblinder, self.n), self.e,
+                                  self.n)
+
+        #Blind the input
+        m = (m * self.blinder) % self.n
+
+        #Perform the RSA operation
+        c = self._rawPrivateKeyOpHelper(m)
+
+        #Unblind the output
+        c = (c * self.unblinder) % self.n
+
+        #Update blinding values
+        self.blinder = (self.blinder * self.blinder) % self.n
+        self.unblinder = (self.unblinder * self.unblinder) % self.n
+
+        #Return the output
+        return c
+
+
+    def _rawPrivateKeyOpHelper(self, m):
+        #Non-CRT version
+        #c = powMod(m, self.d, self.n)
+
+        #CRT version  (~3x faster)
+        s1 = powMod(m, self.dP, self.p)
+        s2 = powMod(m, self.dQ, self.q)
+        h = ((s1 - s2) * self.qInv) % self.p
+        c = s2 + self.q * h
+        return c
+
+    def _rawPublicKeyOp(self, c):
+        m = powMod(c, self.e, self.n)
+        return m
+
+    def acceptsPassword(self): return False
+
+    def write(self, indent=''):
+        if self.d:
+            s = indent+'<privateKey xmlns="http://trevp.net/rsa";>\n'
+        else:
+            s = indent+'<publicKey xmlns="http://trevp.net/rsa";>\n'
+        s += indent+'\t<n>%s</n>\n' % numberToBase64(self.n)
+        s += indent+'\t<e>%s</e>\n' % numberToBase64(self.e)
+        if self.d:
+            s += indent+'\t<d>%s</d>\n' % numberToBase64(self.d)
+            s += indent+'\t<p>%s</p>\n' % numberToBase64(self.p)
+            s += indent+'\t<q>%s</q>\n' % numberToBase64(self.q)
+            s += indent+'\t<dP>%s</dP>\n' % numberToBase64(self.dP)
+            s += indent+'\t<dQ>%s</dQ>\n' % numberToBase64(self.dQ)
+            s += indent+'\t<qInv>%s</qInv>\n' % numberToBase64(self.qInv)
+            s += indent+'</privateKey>'
+        else:
+            s += indent+'</publicKey>'
+        #Only add \n if part of a larger structure
+        if indent != '':
+            s += '\n'
+        return s
+
+    def writeXMLPublicKey(self, indent=''):
+        return Python_RSAKey(self.n, self.e).write(indent)
+
+    def generate(bits):
+        key = Python_RSAKey()
+        p = getRandomPrime(bits/2, False)
+        q = getRandomPrime(bits/2, False)
+        t = lcm(p-1, q-1)
+        key.n = p * q
+        key.e = 3L  #Needed to be long, for Java
+        key.d = invMod(key.e, t)
+        key.p = p
+        key.q = q
+        key.dP = key.d % (p-1)
+        key.dQ = key.d % (q-1)
+        key.qInv = invMod(q, p)
+        return key
+    generate = staticmethod(generate)
+
+    def parsePEM(s, passwordCallback=None):
+        """Parse a string containing a <privateKey> or <publicKey>, or
+        PEM-encoded key."""
+
+        start = s.find("-----BEGIN PRIVATE KEY-----")
+        if start != -1:
+            end = s.find("-----END PRIVATE KEY-----")
+            if end == -1:
+                raise SyntaxError("Missing PEM Postfix")
+            s = s[start+len("-----BEGIN PRIVATE KEY -----") : end]
+            bytes = base64ToBytes(s)
+            return Python_RSAKey._parsePKCS8(bytes)
+        else:
+            start = s.find("-----BEGIN RSA PRIVATE KEY-----")
+            if start != -1:
+                end = s.find("-----END RSA PRIVATE KEY-----")
+                if end == -1:
+                    raise SyntaxError("Missing PEM Postfix")
+                s = s[start+len("-----BEGIN RSA PRIVATE KEY -----") : end]
+                bytes = base64ToBytes(s)
+                return Python_RSAKey._parseSSLeay(bytes)
+        raise SyntaxError("Missing PEM Prefix")
+    parsePEM = staticmethod(parsePEM)
+
+    def parseXML(s):
+        element = xmltools.parseAndStripWhitespace(s)
+        return Python_RSAKey._parseXML(element)
+    parseXML = staticmethod(parseXML)
+
+    def _parsePKCS8(bytes):
+        p = ASN1Parser(bytes)
+
+        version = p.getChild(0).value[0]
+        if version != 0:
+            raise SyntaxError("Unrecognized PKCS8 version")
+
+        rsaOID = p.getChild(1).value
+        if list(rsaOID) != [6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0]:
+            raise SyntaxError("Unrecognized AlgorithmIdentifier")
+
+        #Get the privateKey
+        privateKeyP = p.getChild(2)
+
+        #Adjust for OCTET STRING encapsulation
+        privateKeyP = ASN1Parser(privateKeyP.value)
+
+        return Python_RSAKey._parseASN1PrivateKey(privateKeyP)
+    _parsePKCS8 = staticmethod(_parsePKCS8)
+
+    def _parseSSLeay(bytes):
+        privateKeyP = ASN1Parser(bytes)
+        return Python_RSAKey._parseASN1PrivateKey(privateKeyP)
+    _parseSSLeay = staticmethod(_parseSSLeay)
+
+    def _parseASN1PrivateKey(privateKeyP):
+        version = privateKeyP.getChild(0).value[0]
+        if version != 0:
+            raise SyntaxError("Unrecognized RSAPrivateKey version")
+        n = bytesToNumber(privateKeyP.getChild(1).value)
+        e = bytesToNumber(privateKeyP.getChild(2).value)
+        d = bytesToNumber(privateKeyP.getChild(3).value)
+        p = bytesToNumber(privateKeyP.getChild(4).value)
+        q = bytesToNumber(privateKeyP.getChild(5).value)
+        dP = bytesToNumber(privateKeyP.getChild(6).value)
+        dQ = bytesToNumber(privateKeyP.getChild(7).value)
+        qInv = bytesToNumber(privateKeyP.getChild(8).value)
+        return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv)
+    _parseASN1PrivateKey = staticmethod(_parseASN1PrivateKey)
+
+    def _parseXML(element):
+        try:
+            xmltools.checkName(element, "privateKey")
+        except SyntaxError:
+            xmltools.checkName(element, "publicKey")
+
+        #Parse attributes
+        xmltools.getReqAttribute(element, "xmlns", "http://trevp.net/rsa\Z";)
+        xmltools.checkNoMoreAttributes(element)
+
+        #Parse public values (<n> and <e>)
+        n = base64ToNumber(xmltools.getText(xmltools.getChild(element, 0, "n"), xmltools.base64RegEx))
+        e = base64ToNumber(xmltools.getText(xmltools.getChild(element, 1, "e"), xmltools.base64RegEx))
+        d = 0
+        p = 0
+        q = 0
+        dP = 0
+        dQ = 0
+        qInv = 0
+        #Parse private values, if present
+        if element.childNodes.length>=3:
+            d = base64ToNumber(xmltools.getText(xmltools.getChild(element, 2, "d"), xmltools.base64RegEx))
+            p = base64ToNumber(xmltools.getText(xmltools.getChild(element, 3, "p"), xmltools.base64RegEx))
+            q = base64ToNumber(xmltools.getText(xmltools.getChild(element, 4, "q"), xmltools.base64RegEx))
+            dP = base64ToNumber(xmltools.getText(xmltools.getChild(element, 5, "dP"), xmltools.base64RegEx))
+            dQ = base64ToNumber(xmltools.getText(xmltools.getChild(element, 6, "dQ"), xmltools.base64RegEx))
+            qInv = base64ToNumber(xmltools.getText(xmltools.getLastChild(element, 7, "qInv"), xmltools.base64RegEx))
+        return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv)
+    _parseXML = staticmethod(_parseXML)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/RC4.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/RC4.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,17 @@
+"""Abstract class for RC4."""
+
+from compat import * #For False
+
+class RC4:
+    def __init__(self, keyBytes, implementation):
+        if len(keyBytes) < 16 or len(keyBytes) > 256:
+            raise ValueError()
+        self.isBlockCipher = False
+        self.name = "rc4"
+        self.implementation = implementation
+
+    def encrypt(self, plaintext):
+        raise NotImplementedError()
+
+    def decrypt(self, ciphertext):
+        raise NotImplementedError()
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/RSAKey.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/RSAKey.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,264 @@
+"""Abstract class for RSA."""
+
+from cryptomath import *
+
+
+class RSAKey:
+    """This is an abstract base class for RSA keys.
+
+    Particular implementations of RSA keys, such as
+    L{OpenSSL_RSAKey.OpenSSL_RSAKey},
+    L{Python_RSAKey.Python_RSAKey}, and
+    L{PyCrypto_RSAKey.PyCrypto_RSAKey},
+    inherit from this.
+
+    To create or parse an RSA key, don't use one of these classes
+    directly.  Instead, use the factory functions in
+    L{tlslite.utils.keyfactory}.
+    """
+
+    def __init__(self, n=0, e=0):
+        """Create a new RSA key.
+
+        If n and e are passed in, the new key will be initialized.
+
+        @type n: int
+        @param n: RSA modulus.
+
+        @type e: int
+        @param e: RSA public exponent.
+        """
+        raise NotImplementedError()
+
+    def __len__(self):
+        """Return the length of this key in bits.
+
+        @rtype: int
+        """
+        return numBits(self.n)
+
+    def hasPrivateKey(self):
+        """Return whether or not this key has a private component.
+
+        @rtype: bool
+        """
+        raise NotImplementedError()
+
+    def hash(self):
+        """Return the cryptoID <keyHash> value corresponding to this
+        key.
+
+        @rtype: str
+        """
+        raise NotImplementedError()
+
+    def getSigningAlgorithm(self):
+        """Return the cryptoID sigAlgo value corresponding to this key.
+
+        @rtype: str
+        """
+        return "pkcs1-sha1"
+
+    def hashAndSign(self, bytes):
+        """Hash and sign the passed-in bytes.
+
+        This requires the key to have a private component.  It performs
+        a PKCS1-SHA1 signature on the passed-in data.
+
+        @type bytes: str or L{array.array} of unsigned bytes
+        @param bytes: The value which will be hashed and signed.
+
+        @rtype: L{array.array} of unsigned bytes.
+        @return: A PKCS1-SHA1 signature on the passed-in data.
+        """
+        if not isinstance(bytes, type("")):
+            bytes = bytesToString(bytes)
+        hashBytes = stringToBytes(sha.sha(bytes).digest())
+        prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes)
+        sigBytes = self.sign(prefixedHashBytes)
+        return sigBytes
+
+    def hashAndVerify(self, sigBytes, bytes):
+        """Hash and verify the passed-in bytes with the signature.
+
+        This verifies a PKCS1-SHA1 signature on the passed-in data.
+
+        @type sigBytes: L{array.array} of unsigned bytes
+        @param sigBytes: A PKCS1-SHA1 signature.
+
+        @type bytes: str or L{array.array} of unsigned bytes
+        @param bytes: The value which will be hashed and verified.
+
+        @rtype: bool
+        @return: Whether the signature matches the passed-in data.
+        """
+        if not isinstance(bytes, type("")):
+            bytes = bytesToString(bytes)
+        hashBytes = stringToBytes(sha.sha(bytes).digest())
+        prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes)
+        return self.verify(sigBytes, prefixedHashBytes)
+
+    def sign(self, bytes):
+        """Sign the passed-in bytes.
+
+        This requires the key to have a private component.  It performs
+        a PKCS1 signature on the passed-in data.
+
+        @type bytes: L{array.array} of unsigned bytes
+        @param bytes: The value which will be signed.
+
+        @rtype: L{array.array} of unsigned bytes.
+        @return: A PKCS1 signature on the passed-in data.
+        """
+        if not self.hasPrivateKey():
+            raise AssertionError()
+        paddedBytes = self._addPKCS1Padding(bytes, 1)
+        m = bytesToNumber(paddedBytes)
+        if m >= self.n:
+            raise ValueError()
+        c = self._rawPrivateKeyOp(m)
+        sigBytes = numberToBytes(c)
+        return sigBytes
+
+    def verify(self, sigBytes, bytes):
+        """Verify the passed-in bytes with the signature.
+
+        This verifies a PKCS1 signature on the passed-in data.
+
+        @type sigBytes: L{array.array} of unsigned bytes
+        @param sigBytes: A PKCS1 signature.
+
+        @type bytes: L{array.array} of unsigned bytes
+        @param bytes: The value which will be verified.
+
+        @rtype: bool
+        @return: Whether the signature matches the passed-in data.
+        """
+        paddedBytes = self._addPKCS1Padding(bytes, 1)
+        c = bytesToNumber(sigBytes)
+        if c >= self.n:
+            return False
+        m = self._rawPublicKeyOp(c)
+        checkBytes = numberToBytes(m)
+        return checkBytes == paddedBytes
+
+    def encrypt(self, bytes):
+        """Encrypt the passed-in bytes.
+
+        This performs PKCS1 encryption of the passed-in data.
+
+        @type bytes: L{array.array} of unsigned bytes
+        @param bytes: The value which will be encrypted.
+
+        @rtype: L{array.array} of unsigned bytes.
+        @return: A PKCS1 encryption of the passed-in data.
+        """
+        paddedBytes = self._addPKCS1Padding(bytes, 2)
+        m = bytesToNumber(paddedBytes)
+        if m >= self.n:
+            raise ValueError()
+        c = self._rawPublicKeyOp(m)
+        encBytes = numberToBytes(c)
+        return encBytes
+
+    def decrypt(self, encBytes):
+        """Decrypt the passed-in bytes.
+
+        This requires the key to have a private component.  It performs
+        PKCS1 decryption of the passed-in data.
+
+        @type encBytes: L{array.array} of unsigned bytes
+        @param encBytes: The value which will be decrypted.
+
+        @rtype: L{array.array} of unsigned bytes or None.
+        @return: A PKCS1 decryption of the passed-in data or None if
+        the data is not properly formatted.
+        """
+        if not self.hasPrivateKey():
+            raise AssertionError()
+        c = bytesToNumber(encBytes)
+        if c >= self.n:
+            return None
+        m = self._rawPrivateKeyOp(c)
+        decBytes = numberToBytes(m)
+        if (len(decBytes) != numBytes(self.n)-1): #Check first byte
+            return None
+        if decBytes[0] != 2: #Check second byte
+            return None
+        for x in range(len(decBytes)-1): #Scan through for zero separator
+            if decBytes[x]== 0:
+                break
+        else:
+            return None
+        return decBytes[x+1:] #Return everything after the separator
+
+    def _rawPrivateKeyOp(self, m):
+        raise NotImplementedError()
+
+    def _rawPublicKeyOp(self, c):
+        raise NotImplementedError()
+
+    def acceptsPassword(self):
+        """Return True if the write() method accepts a password for use
+        in encrypting the private key.
+
+        @rtype: bool
+        """
+        raise NotImplementedError()
+
+    def write(self, password=None):
+        """Return a string containing the key.
+
+        @rtype: str
+        @return: A string describing the key, in whichever format (PEM
+        or XML) is native to the implementation.
+        """
+        raise NotImplementedError()
+
+    def writeXMLPublicKey(self, indent=''):
+        """Return a string containing the key.
+
+        @rtype: str
+        @return: A string describing the public key, in XML format.
+        """
+        return Python_RSAKey(self.n, self.e).write(indent)
+
+    def generate(bits):
+        """Generate a new key with the specified bit length.
+
+        @rtype: L{tlslite.utils.RSAKey.RSAKey}
+        """
+        raise NotImplementedError()
+    generate = staticmethod(generate)
+
+
+    # **************************************************************************
+    # Helper Functions for RSA Keys
+    # **************************************************************************
+
+    def _addPKCS1SHA1Prefix(self, bytes):
+        prefixBytes = createByteArraySequence(\
+            [48,33,48,9,6,5,43,14,3,2,26,5,0,4,20])
+        prefixedBytes = prefixBytes + bytes
+        return prefixedBytes
+
+    def _addPKCS1Padding(self, bytes, blockType):
+        padLength = (numBytes(self.n) - (len(bytes)+3))
+        if blockType == 1: #Signature padding
+            pad = [0xFF] * padLength
+        elif blockType == 2: #Encryption padding
+            pad = createByteArraySequence([])
+            while len(pad) < padLength:
+                padBytes = getRandomBytes(padLength * 2)
+                pad = [b for b in padBytes if b != 0]
+                pad = pad[:padLength]
+        else:
+            raise AssertionError()
+
+        #NOTE: To be proper, we should add [0,blockType].  However,
+        #the zero is lost when the returned padding is converted
+        #to a number, so we don't even bother with it.  Also,
+        #adding it would cause a misalignment in verify()
+        padding = createByteArraySequence([blockType] + pad + [0])
+        paddedBytes = padding + bytes
+        return paddedBytes

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/TripleDES.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/TripleDES.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,26 @@
+"""Abstract class for 3DES."""
+
+from compat import * #For True
+
+class TripleDES:
+    def __init__(self, key, mode, IV, implementation):
+        if len(key) != 24:
+            raise ValueError()
+        if mode != 2:
+            raise ValueError()
+        if len(IV) != 8:
+            raise ValueError()
+        self.isBlockCipher = True
+        self.block_size = 8
+        self.implementation = implementation
+        self.name = "3des"
+
+    #CBC-Mode encryption, returns ciphertext
+    #WARNING: *MAY* modify the input as well
+    def encrypt(self, plaintext):
+        assert(len(plaintext) % 8 == 0)
+
+    #CBC-Mode decryption, returns plaintext
+    #WARNING: *MAY* modify the input as well
+    def decrypt(self, ciphertext):
+        assert(len(ciphertext) % 8 == 0)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,31 @@
+"""Toolkit for crypto and other stuff."""
+
+__all__ = ["AES",
+           "ASN1Parser",
+           "cipherfactory",
+           "codec",
+           "Cryptlib_AES",
+           "Cryptlib_RC4",
+           "Cryptlib_TripleDES",
+           "cryptomath: cryptomath module",
+           "dateFuncs",
+           "hmac",
+           "JCE_RSAKey",
+           "compat",
+           "keyfactory",
+           "OpenSSL_AES",
+           "OpenSSL_RC4",
+           "OpenSSL_RSAKey",
+           "OpenSSL_TripleDES",
+           "PyCrypto_AES",
+           "PyCrypto_RC4",
+           "PyCrypto_RSAKey",
+           "PyCrypto_TripleDES",
+           "Python_AES",
+           "Python_RC4",
+           "Python_RSAKey",
+           "RC4",
+           "rijndael",
+           "RSAKey",
+           "TripleDES",
+           "xmltools"]

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/cipherfactory.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/cipherfactory.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,111 @@
+"""Factory functions for symmetric cryptography."""
+
+import os
+
+import Python_AES
+import Python_RC4
+
+import cryptomath
+
+tripleDESPresent = False
+
+if cryptomath.m2cryptoLoaded:
+    import OpenSSL_AES
+    import OpenSSL_RC4
+    import OpenSSL_TripleDES
+    tripleDESPresent = True
+
+if cryptomath.cryptlibpyLoaded:
+    import Cryptlib_AES
+    import Cryptlib_RC4
+    import Cryptlib_TripleDES
+    tripleDESPresent = True
+
+if cryptomath.pycryptoLoaded:
+    import PyCrypto_AES
+    import PyCrypto_RC4
+    import PyCrypto_TripleDES
+    tripleDESPresent = True
+
+# **************************************************************************
+# Factory Functions for AES
+# **************************************************************************
+
+def createAES(key, IV, implList=None):
+    """Create a new AES object.
+
+    @type key: str
+    @param key: A 16, 24, or 32 byte string.
+
+    @type IV: str
+    @param IV: A 16 byte string
+
+    @rtype: L{tlslite.utils.AES}
+    @return: An AES object.
+    """
+    if implList == None:
+        implList = ["cryptlib", "openssl", "pycrypto", "python"]
+
+    for impl in implList:
+        if impl == "cryptlib" and cryptomath.cryptlibpyLoaded:
+            return Cryptlib_AES.new(key, 2, IV)
+        elif impl == "openssl" and cryptomath.m2cryptoLoaded:
+            return OpenSSL_AES.new(key, 2, IV)
+        elif impl == "pycrypto" and cryptomath.pycryptoLoaded:
+            return PyCrypto_AES.new(key, 2, IV)
+        elif impl == "python":
+            return Python_AES.new(key, 2, IV)
+    raise NotImplementedError()
+
+def createRC4(key, IV, implList=None):
+    """Create a new RC4 object.
+
+    @type key: str
+    @param key: A 16 to 32 byte string.
+
+    @type IV: object
+    @param IV: Ignored, whatever it is.
+
+    @rtype: L{tlslite.utils.RC4}
+    @return: An RC4 object.
+    """
+    if implList == None:
+        implList = ["cryptlib", "openssl", "pycrypto", "python"]
+
+    if len(IV) != 0:
+        raise AssertionError()
+    for impl in implList:
+        if impl == "cryptlib" and cryptomath.cryptlibpyLoaded:
+            return Cryptlib_RC4.new(key)
+        elif impl == "openssl" and cryptomath.m2cryptoLoaded:
+            return OpenSSL_RC4.new(key)
+        elif impl == "pycrypto" and cryptomath.pycryptoLoaded:
+            return PyCrypto_RC4.new(key)
+        elif impl == "python":
+            return Python_RC4.new(key)
+    raise NotImplementedError()
+
+#Create a new TripleDES instance
+def createTripleDES(key, IV, implList=None):
+    """Create a new 3DES object.
+
+    @type key: str
+    @param key: A 24 byte string.
+
+    @type IV: str
+    @param IV: An 8 byte string
+
+    @rtype: L{tlslite.utils.TripleDES}
+    @return: A 3DES object.
+    """
+    if implList == None:
+        implList = ["cryptlib", "openssl", "pycrypto"]
+
+    for impl in implList:
+        if impl == "cryptlib" and cryptomath.cryptlibpyLoaded:
+           return Cryptlib_TripleDES.new(key, 2, IV)
+        elif impl == "openssl" and cryptomath.m2cryptoLoaded:
+            return OpenSSL_TripleDES.new(key, 2, IV)
+        elif impl == "pycrypto" and cryptomath.pycryptoLoaded:
+            return PyCrypto_TripleDES.new(key, 2, IV)
+    raise NotImplementedError()
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/codec.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/codec.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,94 @@
+"""Classes for reading/writing binary data (such as TLS records)."""
+
+from compat import *
+
+class Writer:
+    def __init__(self, length=0):
+        #If length is zero, then this is just a "trial run" to determine length
+        self.index = 0
+        self.bytes = createByteArrayZeros(length)
+
+    def add(self, x, length):
+        if self.bytes:
+            newIndex = self.index+length-1
+            while newIndex >= self.index:
+                self.bytes[newIndex] = x & 0xFF
+                x >>= 8
+                newIndex -= 1
+        self.index += length
+
+    def addFixSeq(self, seq, length):
+        if self.bytes:
+            for e in seq:
+                self.add(e, length)
+        else:
+            self.index += len(seq)*length
+
+    def addVarSeq(self, seq, length, lengthLength):
+        if self.bytes:
+            self.add(len(seq)*length, lengthLength)
+            for e in seq:
+                self.add(e, length)
+        else:
+            self.index += lengthLength + (len(seq)*length)
+
+
+class Parser:
+    def __init__(self, bytes):
+        self.bytes = bytes
+        self.index = 0
+
+    def get(self, length):
+        if self.index + length > len(self.bytes):
+            raise SyntaxError()
+        x = 0
+        for count in range(length):
+            x <<= 8
+            x |= self.bytes[self.index]
+            self.index += 1
+        return x
+
+    def getFixBytes(self, lengthBytes):
+        bytes = self.bytes[self.index : self.index+lengthBytes]
+        self.index += lengthBytes
+        return bytes
+
+    def getVarBytes(self, lengthLength):
+        lengthBytes = self.get(lengthLength)
+        return self.getFixBytes(lengthBytes)
+
+    def getFixList(self, length, lengthList):
+        l = [0] * lengthList
+        for x in range(lengthList):
+            l[x] = self.get(length)
+        return l
+
+    def getVarList(self, length, lengthLength):
+        lengthList = self.get(lengthLength)
+        if lengthList % length != 0:
+            raise SyntaxError()
+        lengthList = int(lengthList/length)
+        l = [0] * lengthList
+        for x in range(lengthList):
+            l[x] = self.get(length)
+        return l
+
+    def startLengthCheck(self, lengthLength):
+        self.lengthCheck = self.get(lengthLength)
+        self.indexCheck = self.index
+
+    def setLengthCheck(self, length):
+        self.lengthCheck = length
+        self.indexCheck = self.index
+
+    def stopLengthCheck(self):
+        if (self.index - self.indexCheck) != self.lengthCheck:
+            raise SyntaxError()
+
+    def atLengthCheck(self):
+        if (self.index - self.indexCheck) < self.lengthCheck:
+            return False
+        elif (self.index - self.indexCheck) == self.lengthCheck:
+            return True
+        else:
+            raise SyntaxError()
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/compat.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/compat.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,140 @@
+"""Miscellaneous functions to mask Python version differences."""
+
+import sys
+import os
+
+if sys.version_info < (2,2):
+    raise AssertionError("Python 2.2 or later required")
+
+if sys.version_info < (2,3):
+
+    def enumerate(collection):
+        return zip(range(len(collection)), collection)
+
+    class Set:
+        def __init__(self, seq=None):
+            self.values = {}
+            if seq:
+                for e in seq:
+                    self.values[e] = None
+
+        def add(self, e):
+            self.values[e] = None
+
+        def discard(self, e):
+            if e in self.values.keys():
+                del(self.values[e])
+
+        def union(self, s):
+            ret = Set()
+            for e in self.values.keys():
+                ret.values[e] = None
+            for e in s.values.keys():
+                ret.values[e] = None
+            return ret
+
+        def issubset(self, other):
+            for e in self.values.keys():
+                if e not in other.values.keys():
+                    return False
+            return True
+
+        def __nonzero__( self):
+            return len(self.values.keys())
+
+        def __contains__(self, e):
+            return e in self.values.keys()
+
+        def __iter__(self):
+            return iter(set.values.keys())
+
+
+if os.name != "java":
+
+    import array
+    def createByteArraySequence(seq):
+        return array.array('B', seq)
+    def createByteArrayZeros(howMany):
+        return array.array('B', [0] * howMany)
+    def concatArrays(a1, a2):
+        return a1+a2
+
+    def bytesToString(bytes):
+        return bytes.tostring()
+    def stringToBytes(s):
+        bytes = createByteArrayZeros(0)
+        bytes.fromstring(s)
+        return bytes
+
+    import math
+    def numBits(n):
+        if n==0:
+            return 0
+        s = "%x" % n
+        return ((len(s)-1)*4) + \
+        {'0':0, '1':1, '2':2, '3':2,
+         '4':3, '5':3, '6':3, '7':3,
+         '8':4, '9':4, 'a':4, 'b':4,
+         'c':4, 'd':4, 'e':4, 'f':4,
+         }[s[0]]
+        return int(math.floor(math.log(n, 2))+1)
+
+    BaseException = Exception
+    import sys
+    import traceback
+    def formatExceptionTrace(e):
+        newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
+        return newStr
+
+else:
+    #Jython 2.1 is missing lots of python 2.3 stuff,
+    #which we have to emulate here:
+    #NOTE: JYTHON SUPPORT NO LONGER WORKS, DUE TO USE OF GENERATORS.
+    #THIS CODE IS LEFT IN SO THAT ONE JYTHON UPDATES TO 2.2, IT HAS A
+    #CHANCE OF WORKING AGAIN.
+
+    import java
+    import jarray
+
+    def createByteArraySequence(seq):
+        if isinstance(seq, type("")): #If it's a string, convert
+            seq = [ord(c) for c in seq]
+        return jarray.array(seq, 'h') #use short instead of bytes, cause bytes are signed
+    def createByteArrayZeros(howMany):
+        return jarray.zeros(howMany, 'h') #use short instead of bytes, cause bytes are signed
+    def concatArrays(a1, a2):
+        l = list(a1)+list(a2)
+        return createByteArraySequence(l)
+
+    #WAY TOO SLOW - MUST BE REPLACED------------
+    def bytesToString(bytes):
+        return "".join([chr(b) for b in bytes])
+
+    def stringToBytes(s):
+        bytes = createByteArrayZeros(len(s))
+        for count, c in enumerate(s):
+            bytes[count] = ord(c)
+        return bytes
+    #WAY TOO SLOW - MUST BE REPLACED------------
+
+    def numBits(n):
+        if n==0:
+            return 0
+        n= 1L * n; #convert to long, if it isn't already
+        return n.__tojava__(java.math.BigInteger).bitLength()
+
+    #Adjust the string to an array of bytes
+    def stringToJavaByteArray(s):
+        bytes = jarray.zeros(len(s), 'b')
+        for count, c in enumerate(s):
+            x = ord(c)
+            if x >= 128: x -= 256
+            bytes[count] = x
+        return bytes
+
+    BaseException = java.lang.Exception
+    import sys
+    import traceback
+    def formatExceptionTrace(e):
+        newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
+        return newStr
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/cryptomath.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/cryptomath.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,400 @@
+"""cryptomath module
+
+This module has basic math/crypto code."""
+
+import os
+import math
+import base64
+import binascii
+import sha
+
+from compat import *
+
+
+# **************************************************************************
+# Load Optional Modules
+# **************************************************************************
+
+# Try to load M2Crypto/OpenSSL
+try:
+    from M2Crypto import m2
+    m2cryptoLoaded = True
+
+except ImportError:
+    m2cryptoLoaded = False
+
+
+# Try to load cryptlib
+try:
+    import cryptlib_py
+    try:
+        cryptlib_py.cryptInit()
+    except cryptlib_py.CryptException, e:
+        #If tlslite and cryptoIDlib are both present,
+        #they might each try to re-initialize this,
+        #so we're tolerant of that.
+        if e[0] != cryptlib_py.CRYPT_ERROR_INITED:
+            raise
+    cryptlibpyLoaded = True
+
+except ImportError:
+    cryptlibpyLoaded = False
+
+#Try to load GMPY
+try:
+    import gmpy
+    gmpyLoaded = True
+except ImportError:
+    gmpyLoaded = False
+
+#Try to load pycrypto
+try:
+    import Crypto.Cipher.AES
+    pycryptoLoaded = True
+except ImportError:
+    pycryptoLoaded = False
+
+
+# **************************************************************************
+# PRNG Functions
+# **************************************************************************
+
+# Get os.urandom PRNG
+try:
+    os.urandom(1)
+    def getRandomBytes(howMany):
+        return stringToBytes(os.urandom(howMany))
+    prngName = "os.urandom"
+
+except:
+    # Else get cryptlib PRNG
+    if cryptlibpyLoaded:
+        def getRandomBytes(howMany):
+            randomKey = cryptlib_py.cryptCreateContext(cryptlib_py.CRYPT_UNUSED,
+                                                       cryptlib_py.CRYPT_ALGO_AES)
+            cryptlib_py.cryptSetAttribute(randomKey,
+                                          cryptlib_py.CRYPT_CTXINFO_MODE,
+                                          cryptlib_py.CRYPT_MODE_OFB)
+            cryptlib_py.cryptGenerateKey(randomKey)
+            bytes = createByteArrayZeros(howMany)
+            cryptlib_py.cryptEncrypt(randomKey, bytes)
+            return bytes
+        prngName = "cryptlib"
+
+    else:
+        #Else get UNIX /dev/urandom PRNG
+        try:
+            devRandomFile = open("/dev/urandom", "rb")
+            def getRandomBytes(howMany):
+                return stringToBytes(devRandomFile.read(howMany))
+            prngName = "/dev/urandom"
+        except IOError:
+            #Else get Win32 CryptoAPI PRNG
+            try:
+                import win32prng
+                def getRandomBytes(howMany):
+                    s = win32prng.getRandomBytes(howMany)
+                    if len(s) != howMany:
+                        raise AssertionError()
+                    return stringToBytes(s)
+                prngName ="CryptoAPI"
+            except ImportError:
+                #Else no PRNG :-(
+                def getRandomBytes(howMany):
+                    raise NotImplementedError("No Random Number Generator "\
+                                              "available.")
+            prngName = "None"
+
+# **************************************************************************
+# Converter Functions
+# **************************************************************************
+
+def bytesToNumber(bytes):
+    total = 0L
+    multiplier = 1L
+    for count in range(len(bytes)-1, -1, -1):
+        byte = bytes[count]
+        total += multiplier * byte
+        multiplier *= 256
+    return total
+
+def numberToBytes(n):
+    howManyBytes = numBytes(n)
+    bytes = createByteArrayZeros(howManyBytes)
+    for count in range(howManyBytes-1, -1, -1):
+        bytes[count] = int(n % 256)
+        n >>= 8
+    return bytes
+
+def bytesToBase64(bytes):
+    s = bytesToString(bytes)
+    return stringToBase64(s)
+
+def base64ToBytes(s):
+    s = base64ToString(s)
+    return stringToBytes(s)
+
+def numberToBase64(n):
+    bytes = numberToBytes(n)
+    return bytesToBase64(bytes)
+
+def base64ToNumber(s):
+    bytes = base64ToBytes(s)
+    return bytesToNumber(bytes)
+
+def stringToNumber(s):
+    bytes = stringToBytes(s)
+    return bytesToNumber(bytes)
+
+def numberToString(s):
+    bytes = numberToBytes(s)
+    return bytesToString(bytes)
+
+def base64ToString(s):
+    try:
+        return base64.decodestring(s)
+    except binascii.Error, e:
+        raise SyntaxError(e)
+    except binascii.Incomplete, e:
+        raise SyntaxError(e)
+
+def stringToBase64(s):
+    return base64.encodestring(s).replace("\n", "")
+
+def mpiToNumber(mpi): #mpi is an openssl-format bignum string
+    if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number
+        raise AssertionError()
+    bytes = stringToBytes(mpi[4:])
+    return bytesToNumber(bytes)
+
+def numberToMPI(n):
+    bytes = numberToBytes(n)
+    ext = 0
+    #If the high-order bit is going to be set,
+    #add an extra byte of zeros
+    if (numBits(n) & 0x7)==0:
+        ext = 1
+    length = numBytes(n) + ext
+    bytes = concatArrays(createByteArrayZeros(4+ext), bytes)
+    bytes[0] = (length >> 24) & 0xFF
+    bytes[1] = (length >> 16) & 0xFF
+    bytes[2] = (length >> 8) & 0xFF
+    bytes[3] = length & 0xFF
+    return bytesToString(bytes)
+
+
+
+# **************************************************************************
+# Misc. Utility Functions
+# **************************************************************************
+
+def numBytes(n):
+    if n==0:
+        return 0
+    bits = numBits(n)
+    return int(math.ceil(bits / 8.0))
+
+def hashAndBase64(s):
+    return stringToBase64(sha.sha(s).digest())
+
+def getBase64Nonce(numChars=22): #defaults to an 132 bit nonce
+    bytes = getRandomBytes(numChars)
+    bytesStr = "".join([chr(b) for b in bytes])
+    return stringToBase64(bytesStr)[:numChars]
+
+
+# **************************************************************************
+# Big Number Math
+# **************************************************************************
+
+def getRandomNumber(low, high):
+    if low >= high:
+        raise AssertionError()
+    howManyBits = numBits(high)
+    howManyBytes = numBytes(high)
+    lastBits = howManyBits % 8
+    while 1:
+        bytes = getRandomBytes(howManyBytes)
+        if lastBits:
+            bytes[0] = bytes[0] % (1 << lastBits)
+        n = bytesToNumber(bytes)
+        if n >= low and n < high:
+            return n
+
+def gcd(a,b):
+    a, b = max(a,b), min(a,b)
+    while b:
+        a, b = b, a % b
+    return a
+
+def lcm(a, b):
+    #This will break when python division changes, but we can't use // cause
+    #of Jython
+    return (a * b) / gcd(a, b)
+
+#Returns inverse of a mod b, zero if none
+#Uses Extended Euclidean Algorithm
+def invMod(a, b):
+    c, d = a, b
+    uc, ud = 1, 0
+    while c != 0:
+        #This will break when python division changes, but we can't use //
+        #cause of Jython
+        q = d / c
+        c, d = d-(q*c), c
+        uc, ud = ud - (q * uc), uc
+    if d == 1:
+        return ud % b
+    return 0
+
+
+if gmpyLoaded:
+    def powMod(base, power, modulus):
+        base = gmpy.mpz(base)
+        power = gmpy.mpz(power)
+        modulus = gmpy.mpz(modulus)
+        result = pow(base, power, modulus)
+        return long(result)
+
+else:
+    #Copied from Bryan G. Olson's post to comp.lang.python
+    #Does left-to-right instead of pow()'s right-to-left,
+    #thus about 30% faster than the python built-in with small bases
+    def powMod(base, power, modulus):
+        nBitScan = 5
+
+        """ Return base**power mod modulus, using multi bit scanning
+        with nBitScan bits at a time."""
+
+        #TREV - Added support for negative exponents
+        negativeResult = False
+        if (power < 0):
+            power *= -1
+            negativeResult = True
+
+        exp2 = 2**nBitScan
+        mask = exp2 - 1
+
+        # Break power into a list of digits of nBitScan bits.
+        # The list is recursive so easy to read in reverse direction.
+        nibbles = None
+        while power:
+            nibbles = int(power & mask), nibbles
+            power = power >> nBitScan
+
+        # Make a table of powers of base up to 2**nBitScan - 1
+        lowPowers = [1]
+        for i in xrange(1, exp2):
+            lowPowers.append((lowPowers[i-1] * base) % modulus)
+
+        # To exponentiate by the first nibble, look it up in the table
+        nib, nibbles = nibbles
+        prod = lowPowers[nib]
+
+        # For the rest, square nBitScan times, then multiply by
+        # base^nibble
+        while nibbles:
+            nib, nibbles = nibbles
+            for i in xrange(nBitScan):
+                prod = (prod * prod) % modulus
+            if nib: prod = (prod * lowPowers[nib]) % modulus
+
+        #TREV - Added support for negative exponents
+        if negativeResult:
+            prodInv = invMod(prod, modulus)
+            #Check to make sure the inverse is correct
+            if (prod * prodInv) % modulus != 1:
+                raise AssertionError()
+            return prodInv
+        return prod
+
+
+#Pre-calculate a sieve of the ~100 primes < 1000:
+def makeSieve(n):
+    sieve = range(n)
+    for count in range(2, int(math.sqrt(n))):
+        if sieve[count] == 0:
+            continue
+        x = sieve[count] * 2
+        while x < len(sieve):
+            sieve[x] = 0
+            x += sieve[count]
+    sieve = [x for x in sieve[2:] if x]
+    return sieve
+
+sieve = makeSieve(1000)
+
+def isPrime(n, iterations=5, display=False):
+    #Trial division with sieve
+    for x in sieve:
+        if x >= n: return True
+        if n % x == 0: return False
+    #Passed trial division, proceed to Rabin-Miller
+    #Rabin-Miller implemented per Ferguson & Schneier
+    #Compute s, t for Rabin-Miller
+    if display: print "*",
+    s, t = n-1, 0
+    while s % 2 == 0:
+        s, t = s/2, t+1
+    #Repeat Rabin-Miller x times
+    a = 2 #Use 2 as a base for first iteration speedup, per HAC
+    for count in range(iterations):
+        v = powMod(a, s, n)
+        if v==1:
+            continue
+        i = 0
+        while v != n-1:
+            if i == t-1:
+                return False
+            else:
+                v, i = powMod(v, 2, n), i+1
+        a = getRandomNumber(2, n)
+    return True
+
+def getRandomPrime(bits, display=False):
+    if bits < 10:
+        raise AssertionError()
+    #The 1.5 ensures the 2 MSBs are set
+    #Thus, when used for p,q in RSA, n will have its MSB set
+    #
+    #Since 30 is lcm(2,3,5), we'll set our test numbers to
+    #29 % 30 and keep them there
+    low = (2L ** (bits-1)) * 3/2
+    high = 2L ** bits - 30
+    p = getRandomNumber(low, high)
+    p += 29 - (p % 30)
+    while 1:
+        if display: print ".",
+        p += 30
+        if p >= high:
+            p = getRandomNumber(low, high)
+            p += 29 - (p % 30)
+        if isPrime(p, display=display):
+            return p
+
+#Unused at the moment...
+def getRandomSafePrime(bits, display=False):
+    if bits < 10:
+        raise AssertionError()
+    #The 1.5 ensures the 2 MSBs are set
+    #Thus, when used for p,q in RSA, n will have its MSB set
+    #
+    #Since 30 is lcm(2,3,5), we'll set our test numbers to
+    #29 % 30 and keep them there
+    low = (2 ** (bits-2)) * 3/2
+    high = (2 ** (bits-1)) - 30
+    q = getRandomNumber(low, high)
+    q += 29 - (q % 30)
+    while 1:
+        if display: print ".",
+        q += 30
+        if (q >= high):
+            q = getRandomNumber(low, high)
+            q += 29 - (q % 30)
+        #Ideas from Tom Wu's SRP code
+        #Do trial division on p and q before Rabin-Miller
+        if isPrime(q, 0, display=display):
+            p = (2 * q) + 1
+            if isPrime(p, display=display):
+                if isPrime(q, display=display):
+                    return p

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/dateFuncs.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/dateFuncs.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,75 @@
+
+import os
+
+#Functions for manipulating datetime objects
+#CCYY-MM-DDThh:mm:ssZ
+def parseDateClass(s):
+    year, month, day = s.split("-")
+    day, tail = day[:2], day[2:]
+    hour, minute, second = tail[1:].split(":")
+    second = second[:2]
+    year, month, day = int(year), int(month), int(day)
+    hour, minute, second = int(hour), int(minute), int(second)
+    return createDateClass(year, month, day, hour, minute, second)
+
+
+if os.name != "java":
+    from datetime import datetime, timedelta
+
+    #Helper functions for working with a date/time class
+    def createDateClass(year, month, day, hour, minute, second):
+        return datetime(year, month, day, hour, minute, second)
+
+    def printDateClass(d):
+        #Split off fractional seconds, append 'Z'
+        return d.isoformat().split(".")[0]+"Z"
+
+    def getNow():
+        return datetime.utcnow()
+
+    def getHoursFromNow(hours):
+        return datetime.utcnow() + timedelta(hours=hours)
+
+    def getMinutesFromNow(minutes):
+        return datetime.utcnow() + timedelta(minutes=minutes)
+
+    def isDateClassExpired(d):
+        return d < datetime.utcnow()
+
+    def isDateClassBefore(d1, d2):
+        return d1 < d2
+
+else:
+    #Jython 2.1 is missing lots of python 2.3 stuff,
+    #which we have to emulate here:
+    import java
+    import jarray
+
+    def createDateClass(year, month, day, hour, minute, second):
+        c = java.util.Calendar.getInstance()
+        c.setTimeZone(java.util.TimeZone.getTimeZone("UTC"))
+        c.set(year, month-1, day, hour, minute, second)
+        return c
+
+    def printDateClass(d):
+        return "%04d-%02d-%02dT%02d:%02d:%02dZ" % \
+        (d.get(d.YEAR), d.get(d.MONTH)+1, d.get(d.DATE), \
+        d.get(d.HOUR_OF_DAY), d.get(d.MINUTE), d.get(d.SECOND))
+
+    def getNow():
+        c = java.util.Calendar.getInstance()
+        c.setTimeZone(java.util.TimeZone.getTimeZone("UTC"))
+        c.get(c.HOUR) #force refresh?
+        return c
+
+    def getHoursFromNow(hours):
+        d = getNow()
+        d.add(d.HOUR, hours)
+        return d
+
+    def isDateClassExpired(d):
+        n = getNow()
+        return d.before(n)
+
+    def isDateClassBefore(d1, d2):
+        return d1.before(d2)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/entropy.c
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/entropy.c	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,173 @@
+
+#include "Python.h"
+
+
+#ifdef MS_WINDOWS
+
+/* The following #define is not needed on VC6 with the Platform SDK, and it
+may not be needed on VC7, I'm not sure.  I don't think it hurts anything.*/
+#define _WIN32_WINNT 0x0400
+
+#include <windows.h>
+
+
+typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,\
+              LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\
+              DWORD dwFlags );
+typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,\
+              BYTE *pbBuffer );
+typedef BOOL (WINAPI *CRYPTRELEASECONTEXT)(HCRYPTPROV hProv,\
+              DWORD dwFlags);
+
+
+static PyObject* entropy(PyObject *self, PyObject *args)
+{
+    int howMany = 0;
+    HINSTANCE hAdvAPI32 = NULL;
+    CRYPTACQUIRECONTEXTA pCryptAcquireContextA = NULL;
+    CRYPTGENRANDOM pCryptGenRandom = NULL;
+    CRYPTRELEASECONTEXT pCryptReleaseContext = NULL;
+    HCRYPTPROV hCryptProv = 0;
+    unsigned char* bytes = NULL;
+    PyObject* returnVal = NULL;
+
+
+    /* Read arguments */
+    if (!PyArg_ParseTuple(args, "i", &howMany))
+        return(NULL);
+
+	/* Obtain handle to the DLL containing CryptoAPI
+	   This should not fail	*/
+	if( (hAdvAPI32 = GetModuleHandle("advapi32.dll")) == NULL) {
+        PyErr_Format(PyExc_SystemError,
+            "Advapi32.dll not found");
+        return NULL;
+	}
+
+	/* Obtain pointers to the CryptoAPI functions
+	   This will fail on some early version of Win95 */
+	pCryptAcquireContextA = (CRYPTACQUIRECONTEXTA)GetProcAddress(hAdvAPI32,\
+	                        "CryptAcquireContextA");
+	pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress(hAdvAPI32,\
+	                  "CryptGenRandom");
+	pCryptReleaseContext = (CRYPTRELEASECONTEXT) GetProcAddress(hAdvAPI32,\
+	                       "CryptReleaseContext");
+	if (pCryptAcquireContextA == NULL || pCryptGenRandom == NULL ||
+	                                     pCryptReleaseContext == NULL) {
+        PyErr_Format(PyExc_NotImplementedError,
+            "CryptoAPI not available on this version of Windows");
+        return NULL;
+	}
+
+    /* Allocate bytes */
+    if ((bytes = (unsigned char*)PyMem_Malloc(howMany)) == NULL)
+        return PyErr_NoMemory();
+
+
+    /* Acquire context */
+    if(!pCryptAcquireContextA(&hCryptProv, NULL, NULL, PROV_RSA_FULL,
+                            CRYPT_VERIFYCONTEXT)) {
+        PyErr_Format(PyExc_SystemError,
+                     "CryptAcquireContext failed, error %d", GetLastError());
+        PyMem_Free(bytes);
+        return NULL;
+    }
+
+    /* Get random data */
+    if(!pCryptGenRandom(hCryptProv, howMany, bytes)) {
+        PyErr_Format(PyExc_SystemError,
+                     "CryptGenRandom failed, error %d", GetLastError());
+        PyMem_Free(bytes);
+        CryptReleaseContext(hCryptProv, 0);
+        return NULL;
+    }
+
+    /* Build return value */
+    returnVal = Py_BuildValue("s#", bytes, howMany);
+    PyMem_Free(bytes);
+
+    /* Release context */
+    if (!pCryptReleaseContext(hCryptProv, 0)) {
+        PyErr_Format(PyExc_SystemError,
+                     "CryptReleaseContext failed, error %d", GetLastError());
+        return NULL;
+    }
+
+    return returnVal;
+}
+
+#elif defined(HAVE_UNISTD_H) && defined(HAVE_FCNTL_H)
+
+#include <unistd.h>
+#include <fcntl.h>
+
+static PyObject* entropy(PyObject *self, PyObject *args)
+{
+    int howMany;
+    int fd;
+    unsigned char* bytes = NULL;
+    PyObject* returnVal = NULL;
+
+
+    /* Read arguments */
+    if (!PyArg_ParseTuple(args, "i", &howMany))
+        return(NULL);
+
+    /* Allocate bytes */
+    if ((bytes = (unsigned char*)PyMem_Malloc(howMany)) == NULL)
+        return PyErr_NoMemory();
+
+    /* Open device */
+    if ((fd = open("/dev/urandom", O_RDONLY, 0)) == -1) {
+        PyErr_Format(PyExc_NotImplementedError,
+            "No entropy source found");
+        PyMem_Free(bytes);
+        return NULL;
+    }
+
+    /* Get random data */
+    if (read(fd, bytes, howMany) < howMany) {
+        PyErr_Format(PyExc_SystemError,
+            "Reading from /dev/urandom failed");
+        PyMem_Free(bytes);
+        close(fd);
+        return NULL;
+    }
+
+    /* Build return value */
+    returnVal = Py_BuildValue("s#", bytes, howMany);
+    PyMem_Free(bytes);
+
+    /* Close device */
+    close(fd);
+
+    return returnVal;
+}
+
+#else
+
+static PyObject* entropy(PyObject *self, PyObject *args)
+{
+    PyErr_Format(PyExc_NotImplementedError,
+            "Function not supported");
+    return NULL;
+}
+
+#endif
+
+
+
+/* List of functions exported by this module */
+
+static struct PyMethodDef entropy_functions[] = {
+    {"entropy", (PyCFunction)entropy, METH_VARARGS, "Return a string of random bytes produced by a platform-specific\nentropy source."},
+    {NULL,  NULL}        /* Sentinel */
+};
+
+
+/* Initialize this module. */
+
+PyMODINIT_FUNC initentropy(void)
+{
+    Py_InitModule("entropy", entropy_functions);
+}
\ No newline at end of file

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/hmac.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/hmac.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,104 @@
+"""HMAC (Keyed-Hashing for Message Authentication) Python module.
+
+Implements the HMAC algorithm as described by RFC 2104.
+
+(This file is modified from the standard library version to do faster
+copying)
+"""
+
+def _strxor(s1, s2):
+    """Utility method. XOR the two strings s1 and s2 (must have same length).
+    """
+    return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), s1, s2))
+
+# The size of the digests returned by HMAC depends on the underlying
+# hashing module used.
+digest_size = None
+
+class HMAC:
+    """RFC2104 HMAC class.
+
+    This supports the API for Cryptographic Hash Functions (PEP 247).
+    """
+
+    def __init__(self, key, msg = None, digestmod = None):
+        """Create a new HMAC object.
+
+        key:       key for the keyed hash object.
+        msg:       Initial input for the hash, if provided.
+        digestmod: A module supporting PEP 247. Defaults to the md5 module.
+        """
+        if digestmod is None:
+            import md5
+            digestmod = md5
+
+        if key == None: #TREVNEW - for faster copying
+            return      #TREVNEW
+
+        self.digestmod = digestmod
+        self.outer = digestmod.new()
+        self.inner = digestmod.new()
+        self.digest_size = digestmod.digest_size
+
+        blocksize = 64
+        ipad = "\x36" * blocksize
+        opad = "\x5C" * blocksize
+
+        if len(key) > blocksize:
+            key = digestmod.new(key).digest()
+
+        key = key + chr(0) * (blocksize - len(key))
+        self.outer.update(_strxor(key, opad))
+        self.inner.update(_strxor(key, ipad))
+        if msg is not None:
+            self.update(msg)
+
+##    def clear(self):
+##        raise NotImplementedError, "clear() method not available in HMAC."
+
+    def update(self, msg):
+        """Update this hashing object with the string msg.
+        """
+        self.inner.update(msg)
+
+    def copy(self):
+        """Return a separate copy of this hashing object.
+
+        An update to this copy won't affect the original object.
+        """
+        other = HMAC(None) #TREVNEW - for faster copying
+        other.digest_size = self.digest_size #TREVNEW
+        other.digestmod = self.digestmod
+        other.inner = self.inner.copy()
+        other.outer = self.outer.copy()
+        return other
+
+    def digest(self):
+        """Return the hash value of this hashing object.
+
+        This returns a string containing 8-bit data.  The object is
+        not altered in any way by this function; you can continue
+        updating the object after calling this function.
+        """
+        h = self.outer.copy()
+        h.update(self.inner.digest())
+        return h.digest()
+
+    def hexdigest(self):
+        """Like digest(), but returns a string of hexadecimal digits instead.
+        """
+        return "".join([hex(ord(x))[2:].zfill(2)
+                        for x in tuple(self.digest())])
+
+def new(key, msg = None, digestmod = None):
+    """Create a new hashing object and return it.
+
+    key: The starting key for the hash.
+    msg: if available, will immediately be hashed into the object's starting
+    state.
+
+    You can now feed arbitrary strings into the object using its update()
+    method, and can ask for the hash value at any time by calling its digest()
+    method.
+    """
+    return HMAC(key, msg, digestmod)

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/jython_compat.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/jython_compat.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,195 @@
+"""Miscellaneous functions to mask Python/Jython differences."""
+
+import os
+import sha
+
+if os.name != "java":
+    BaseException = Exception
+
+    from sets import Set
+    import array
+    import math
+
+    def createByteArraySequence(seq):
+        return array.array('B', seq)
+    def createByteArrayZeros(howMany):
+        return array.array('B', [0] * howMany)
+    def concatArrays(a1, a2):
+        return a1+a2
+
+    def bytesToString(bytes):
+        return bytes.tostring()
+
+    def stringToBytes(s):
+        bytes = createByteArrayZeros(0)
+        bytes.fromstring(s)
+        return bytes
+
+    def numBits(n):
+        if n==0:
+            return 0
+        return int(math.floor(math.log(n, 2))+1)
+
+    class CertChainBase: pass
+    class SelfTestBase: pass
+    class ReportFuncBase: pass
+
+    #Helper functions for working with sets (from Python 2.3)
+    def iterSet(set):
+        return iter(set)
+
+    def getListFromSet(set):
+        return list(set)
+
+    #Factory function for getting a SHA1 object
+    def getSHA1(s):
+        return sha.sha(s)
+
+    import sys
+    import traceback
+
+    def formatExceptionTrace(e):
+        newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
+        return newStr
+
+else:
+    #Jython 2.1 is missing lots of python 2.3 stuff,
+    #which we have to emulate here:
+    import java
+    import jarray
+
+    BaseException = java.lang.Exception
+
+    def createByteArraySequence(seq):
+        if isinstance(seq, type("")): #If it's a string, convert
+            seq = [ord(c) for c in seq]
+        return jarray.array(seq, 'h') #use short instead of bytes, cause bytes are signed
+    def createByteArrayZeros(howMany):
+        return jarray.zeros(howMany, 'h') #use short instead of bytes, cause bytes are signed
+    def concatArrays(a1, a2):
+        l = list(a1)+list(a2)
+        return createByteArraySequence(l)
+
+    #WAY TOO SLOW - MUST BE REPLACED------------
+    def bytesToString(bytes):
+        return "".join([chr(b) for b in bytes])
+
+    def stringToBytes(s):
+        bytes = createByteArrayZeros(len(s))
+        for count, c in enumerate(s):
+            bytes[count] = ord(c)
+        return bytes
+    #WAY TOO SLOW - MUST BE REPLACED------------
+
+    def numBits(n):
+        if n==0:
+            return 0
+        n= 1L * n; #convert to long, if it isn't already
+        return n.__tojava__(java.math.BigInteger).bitLength()
+
+    #This properly creates static methods for Jython
+    class staticmethod:
+        def __init__(self, anycallable): self.__call__ = anycallable
+
+    #Properties are not supported for Jython
+    class property:
+        def __init__(self, anycallable): pass
+
+    #True and False have to be specially defined
+    False = 0
+    True = 1
+
+    class StopIteration(Exception): pass
+
+    def enumerate(collection):
+        return zip(range(len(collection)), collection)
+
+    class Set:
+        def __init__(self, seq=None):
+            self.values = {}
+            if seq:
+                for e in seq:
+                    self.values[e] = None
+
+        def add(self, e):
+            self.values[e] = None
+
+        def discard(self, e):
+            if e in self.values.keys():
+                del(self.values[e])
+
+        def union(self, s):
+            ret = Set()
+            for e in self.values.keys():
+                ret.values[e] = None
+            for e in s.values.keys():
+                ret.values[e] = None
+            return ret
+
+        def issubset(self, other):
+            for e in self.values.keys():
+                if e not in other.values.keys():
+                    return False
+            return True
+
+        def __nonzero__( self):
+            return len(self.values.keys())
+
+        def __contains__(self, e):
+            return e in self.values.keys()
+
+    def iterSet(set):
+        return set.values.keys()
+
+    def getListFromSet(set):
+        return set.values.keys()
+
+    """
+    class JCE_SHA1:
+        def __init__(self, s=None):
+            self.md = java.security.MessageDigest.getInstance("SHA1")
+            if s:
+                self.update(s)
+
+        def update(self, s):
+            self.md.update(s)
+
+        def copy(self):
+            sha1 = JCE_SHA1()
+            sha1.md = self.md.clone()
+            return sha1
+
+        def digest(self):
+            digest = self.md.digest()
+            bytes = jarray.zeros(20, 'h')
+            for count in xrange(20):
+                x = digest[count]
+                if x < 0: x += 256
+                bytes[count] = x
+            return bytes
+    """
+
+    #Factory function for getting a SHA1 object
+    #The JCE_SHA1 class is way too slow...
+    #the sha.sha object we use instead is broken in the jython 2.1
+    #release, and needs to be patched
+    def getSHA1(s):
+        #return JCE_SHA1(s)
+        return sha.sha(s)
+
+
+    #Adjust the string to an array of bytes
+    def stringToJavaByteArray(s):
+        bytes = jarray.zeros(len(s), 'b')
+        for count, c in enumerate(s):
+            x = ord(c)
+            if x >= 128: x -= 256
+            bytes[count] = x
+        return bytes
+
+    import sys
+    import traceback
+
+    def formatExceptionTrace(e):
+        newStr = "".join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
+        return newStr

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/keyfactory.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/keyfactory.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,243 @@
+"""Factory functions for asymmetric cryptography.
+ sort: generateRSAKey, parseXMLKey, parsePEMKey, parseAsPublicKey,
+parseAsPrivateKey
+"""
+
+from compat import *
+
+from RSAKey import RSAKey
+from Python_RSAKey import Python_RSAKey
+import cryptomath
+
+if cryptomath.m2cryptoLoaded:
+    from OpenSSL_RSAKey import OpenSSL_RSAKey
+
+if cryptomath.pycryptoLoaded:
+    from PyCrypto_RSAKey import PyCrypto_RSAKey
+
+# **************************************************************************
+# Factory Functions for RSA Keys
+# **************************************************************************
+
+def generateRSAKey(bits, implementations=["openssl", "python"]):
+    """Generate an RSA key with the specified bit length.
+
+    @type bits: int
+    @param bits: Desired bit length of the new key's modulus.
+
+    @rtype: L{tlslite.utils.RSAKey.RSAKey}
+    @return: A new RSA private key.
+    """
+    for implementation in implementations:
+        if implementation == "openssl" and cryptomath.m2cryptoLoaded:
+            return OpenSSL_RSAKey.generate(bits)
+        elif implementation == "python":
+            return Python_RSAKey.generate(bits)
+    raise ValueError("No acceptable implementations")
+
+def parseXMLKey(s, private=False, public=False, implementations=["python"]):
+    """Parse an XML-format key.
+
+    The XML format used here is specific to tlslite and cryptoIDlib.  The
+    format can store the public component of a key, or the public and
+    private components.  For example::
+
+        <publicKey xmlns="http://trevp.net/rsa";>
+            <n>4a5yzB8oGNlHo866CAspAC47M4Fvx58zwK8pou...
+            <e>Aw==</e>
+        </publicKey>
+
+        <privateKey xmlns="http://trevp.net/rsa";>
+            <n>4a5yzB8oGNlHo866CAspAC47M4Fvx58zwK8pou...
+            <e>Aw==</e>
+            <d>JZ0TIgUxWXmL8KJ0VqyG1V0J3ern9pqIoB0xmy...
+            <p>5PreIj6z6ldIGL1V4+1C36dQFHNCQHJvW52GXc...
+            <q>/E/wDit8YXPCxx126zTq2ilQ3IcW54NJYyNjiZ...
+            <dP>mKc+wX8inDowEH45Qp4slRo1YveBgExKPROu6...
+            <dQ>qDVKtBz9lk0shL5PR3ickXDgkwS576zbl2ztB...
+            <qInv>j6E8EA7dNsTImaXexAmLA1DoeArsYeFAInr...
+        </privateKey>
+
+    @type s: str
+    @param s: A string containing an XML public or private key.
+
+    @type private: bool
+    @param private: If True, a L{SyntaxError} will be raised if the private
+    key component is not present.
+
+    @type public: bool
+    @param public: If True, the private key component (if present) will be
+    discarded, so this function will always return a public key.
+
+    @rtype: L{tlslite.utils.RSAKey.RSAKey}
+    @return: An RSA key.
+
+    @raise SyntaxError: If the key is not properly formatted.
+    """
+    for implementation in implementations:
+        if implementation == "python":
+            key = Python_RSAKey.parseXML(s)
+            break
+    else:
+        raise ValueError("No acceptable implementations")
+
+    return _parseKeyHelper(key, private, public)
+
+#Parse as an OpenSSL or Python key
+def parsePEMKey(s, private=False, public=False, passwordCallback=None,
+                implementations=["openssl", "python"]):
+    """Parse a PEM-format key.
+
+    The PEM format is used by OpenSSL and other tools.  The
+    format is typically used to store both the public and private
+    components of a key.  For example::
+
+       -----BEGIN RSA PRIVATE KEY-----
+        MIICXQIBAAKBgQDYscuoMzsGmW0pAYsmyHltxB2TdwHS0dImfjCMfaSDkfLdZY5+
+        dOWORVns9etWnr194mSGA1F0Pls/VJW8+cX9+3vtJV8zSdANPYUoQf0TP7VlJxkH
+        dSRkUbEoz5bAAs/+970uos7n7iXQIni+3erUTdYEk2iWnMBjTljfgbK/dQIDAQAB
+        AoGAJHoJZk75aKr7DSQNYIHuruOMdv5ZeDuJvKERWxTrVJqE32/xBKh42/IgqRrc
+        esBN9ZregRCd7YtxoL+EVUNWaJNVx2mNmezEznrc9zhcYUrgeaVdFO2yBF1889zO
+        gCOVwrO8uDgeyj6IKa25H6c1N13ih/o7ZzEgWbGG+ylU1yECQQDv4ZSJ4EjSh/Fl
+        aHdz3wbBa/HKGTjC8iRy476Cyg2Fm8MZUe9Yy3udOrb5ZnS2MTpIXt5AF3h2TfYV
+        VoFXIorjAkEA50FcJmzT8sNMrPaV8vn+9W2Lu4U7C+K/O2g1iXMaZms5PC5zV5aV
+        CKXZWUX1fq2RaOzlbQrpgiolhXpeh8FjxwJBAOFHzSQfSsTNfttp3KUpU0LbiVvv
+        i+spVSnA0O4rq79KpVNmK44Mq67hsW1P11QzrzTAQ6GVaUBRv0YS061td1kCQHnP
+        wtN2tboFR6lABkJDjxoGRvlSt4SOPr7zKGgrWjeiuTZLHXSAnCY+/hr5L9Q3ZwXG
+        6x6iBdgLjVIe4BZQNtcCQQDXGv/gWinCNTN3MPWfTW/RGzuMYVmyBFais0/VrgdH
+        h1dLpztmpQqfyH/zrBXQ9qL/zR4ojS6XYneO/U18WpEe
+        -----END RSA PRIVATE KEY-----
+
+    To generate a key like this with OpenSSL, run::
+
+        openssl genrsa 2048 > key.pem
+
+    This format also supports password-encrypted private keys.  TLS
+    Lite can only handle password-encrypted private keys when OpenSSL
+    and M2Crypto are installed.  In this case, passwordCallback will be
+    invoked to query the user for the password.
+
+    @type s: str
+    @param s: A string containing a PEM-encoded public or private key.
+
+    @type private: bool
+    @param private: If True, a L{SyntaxError} will be raised if the
+    private key component is not present.
+
+    @type public: bool
+    @param public: If True, the private key component (if present) will
+    be discarded, so this function will always return a public key.
+
+    @type passwordCallback: callable
+    @param passwordCallback: This function will be called, with no
+    arguments, if the PEM-encoded private key is password-encrypted.
+    The callback should return the password string.  If the password is
+    incorrect, SyntaxError will be raised.  If no callback is passed
+    and the key is password-encrypted, a prompt will be displayed at
+    the console.
+
+    @rtype: L{tlslite.utils.RSAKey.RSAKey}
+    @return: An RSA key.
+
+    @raise SyntaxError: If the key is not properly formatted.
+    """
+    for implementation in implementations:
+        if implementation == "openssl" and cryptomath.m2cryptoLoaded:
+            key = OpenSSL_RSAKey.parse(s, passwordCallback)
+            break
+        elif implementation == "python":
+            key = Python_RSAKey.parsePEM(s)
+            break
+    else:
+        raise ValueError("No acceptable implementations")
+
+    return _parseKeyHelper(key, private, public)
+
+
+def _parseKeyHelper(key, private, public):
+    if private:
+        if not key.hasPrivateKey():
+            raise SyntaxError("Not a private key!")
+
+    if public:
+        return _createPublicKey(key)
+
+    if private:
+        if hasattr(key, "d"):
+            return _createPrivateKey(key)
+        else:
+            return key
+
+    return key
+
+def parseAsPublicKey(s):
+    """Parse an XML or PEM-formatted public key.
+
+    @type s: str
+    @param s: A string containing an XML or PEM-encoded public or private key.
+
+    @rtype: L{tlslite.utils.RSAKey.RSAKey}
+    @return: An RSA public key.
+
+    @raise SyntaxError: If the key is not properly formatted.
+    """
+    try:
+        return parsePEMKey(s, public=True)
+    except:
+        return parseXMLKey(s, public=True)
+
+def parsePrivateKey(s):
+    """Parse an XML or PEM-formatted private key.
+
+    @type s: str
+    @param s: A string containing an XML or PEM-encoded private key.
+
+    @rtype: L{tlslite.utils.RSAKey.RSAKey}
+    @return: An RSA private key.
+
+    @raise SyntaxError: If the key is not properly formatted.
+    """
+    try:
+        return parsePEMKey(s, private=True)
+    except:
+        return parseXMLKey(s, private=True)
+
+def _createPublicKey(key):
+    """
+    Create a new public key.  Discard any private component,
+    and return the most efficient key possible.
+    """
+    if not isinstance(key, RSAKey):
+        raise AssertionError()
+    return _createPublicRSAKey(key.n, key.e)
+
+def _createPrivateKey(key):
+    """
+    Create a new private key.  Return the most efficient key possible.
+    """
+    if not isinstance(key, RSAKey):
+        raise AssertionError()
+    if not key.hasPrivateKey():
+        raise AssertionError()
+    return _createPrivateRSAKey(key.n, key.e, key.d, key.p, key.q, key.dP,
+                                key.dQ, key.qInv)
+
+def _createPublicRSAKey(n, e, implementations = ["openssl", "pycrypto",
+                                                "python"]):
+    for implementation in implementations:
+        if implementation == "openssl" and cryptomath.m2cryptoLoaded:
+            return OpenSSL_RSAKey(n, e)
+        elif implementation == "pycrypto" and cryptomath.pycryptoLoaded:
+            return PyCrypto_RSAKey(n, e)
+        elif implementation == "python":
+            return Python_RSAKey(n, e)
+    raise ValueError("No acceptable implementations")
+
+def _createPrivateRSAKey(n, e, d, p, q, dP, dQ, qInv,
+                        implementations = ["pycrypto", "python"]):
+    for implementation in implementations:
+        if implementation == "pycrypto" and cryptomath.pycryptoLoaded:
+            return PyCrypto_RSAKey(n, e, d, p, q, dP, dQ, qInv)
+        elif implementation == "python":
+            return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv)
+    raise ValueError("No acceptable implementations")

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/rijndael.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/rijndael.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,392 @@
+"""
+A pure python (slow) implementation of rijndael with a decent interface
+
+To include -
+
+from rijndael import rijndael
+
+To do a key setup -
+
+r = rijndael(key, block_size = 16)
+
+key must be a string of length 16, 24, or 32
+blocksize must be 16, 24, or 32. Default is 16
+
+To use -
+
+ciphertext = r.encrypt(plaintext)
+plaintext = r.decrypt(ciphertext)
+
+If any strings are of the wrong length a ValueError is thrown
+"""
+
+# ported from the Java reference code by Bram Cohen, bram gawth com, April 2001
+# this code is public domain, unless someone makes
+# an intellectual property claim against the reference
+# code, in which case it can be made public domain by
+# deleting all the comments and renaming all the variables
+
+import copy
+import string
+
+
+
+#-----------------------
+#TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN
+#2.4.....
+import os
+if os.name != "java":
+    import exceptions
+    if hasattr(exceptions, "FutureWarning"):
+        import warnings
+        warnings.filterwarnings("ignore", category=FutureWarning, append=1)
+#-----------------------
+
+
+
+shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]],
+          [[0, 0], [1, 5], [2, 4], [3, 3]],
+          [[0, 0], [1, 7], [3, 5], [4, 4]]]
+
+# [keysize][block_size]
+num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}}
+
+A = [[1, 1, 1, 1, 1, 0, 0, 0],
+     [0, 1, 1, 1, 1, 1, 0, 0],
+     [0, 0, 1, 1, 1, 1, 1, 0],
+     [0, 0, 0, 1, 1, 1, 1, 1],
+     [1, 0, 0, 0, 1, 1, 1, 1],
+     [1, 1, 0, 0, 0, 1, 1, 1],
+     [1, 1, 1, 0, 0, 0, 1, 1],
+     [1, 1, 1, 1, 0, 0, 0, 1]]
+
+# produce log and alog tables, needed for multiplying in the
+# field GF(2^m) (generator = 3)
+alog = [1]
+for i in xrange(255):
+    j = (alog[-1] << 1) ^ alog[-1]
+    if j & 0x100 != 0:
+        j ^= 0x11B
+    alog.append(j)
+
+log = [0] * 256
+for i in xrange(1, 255):
+    log[alog[i]] = i
+
+# multiply two elements of GF(2^m)
+def mul(a, b):
+    if a == 0 or b == 0:
+        return 0
+    return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255]
+
+# substitution box based on F^{-1}(x)
+box = [[0] * 8 for i in xrange(256)]
+box[1][7] = 1
+for i in xrange(2, 256):
+    j = alog[255 - log[i]]
+    for t in xrange(8):
+        box[i][t] = (j >> (7 - t)) & 0x01
+
+B = [0, 1, 1, 0, 0, 0, 1, 1]
+
+# affine transform:  box[i] <- B + A*box[i]
+cox = [[0] * 8 for i in xrange(256)]
+for i in xrange(256):
+    for t in xrange(8):
+        cox[i][t] = B[t]
+        for j in xrange(8):
+            cox[i][t] ^= A[t][j] * box[i][j]
+
+# S-boxes and inverse S-boxes
+S =  [0] * 256
+Si = [0] * 256
+for i in xrange(256):
+    S[i] = cox[i][0] << 7
+    for t in xrange(1, 8):
+        S[i] ^= cox[i][t] << (7-t)
+    Si[S[i] & 0xFF] = i
+
+# T-boxes
+G = [[2, 1, 1, 3],
+    [3, 2, 1, 1],
+    [1, 3, 2, 1],
+    [1, 1, 3, 2]]
+
+AA = [[0] * 8 for i in xrange(4)]
+
+for i in xrange(4):
+    for j in xrange(4):
+        AA[i][j] = G[i][j]
+        AA[i][i+4] = 1
+
+for i in xrange(4):
+    pivot = AA[i][i]
+    if pivot == 0:
+        t = i + 1
+        while AA[t][i] == 0 and t < 4:
+            t += 1
+            assert t != 4, 'G matrix must be invertible'
+            for j in xrange(8):
+                AA[i][j], AA[t][j] = AA[t][j], AA[i][j]
+            pivot = AA[i][i]
+    for j in xrange(8):
+        if AA[i][j] != 0:
+            AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255]
+    for t in xrange(4):
+        if i != t:
+            for j in xrange(i+1, 8):
+                AA[t][j] ^= mul(AA[i][j], AA[t][i])
+            AA[t][i] = 0
+
+iG = [[0] * 4 for i in xrange(4)]
+
+for i in xrange(4):
+    for j in xrange(4):
+        iG[i][j] = AA[i][j + 4]
+
+def mul4(a, bs):
+    if a == 0:
+        return 0
+    r = 0
+    for b in bs:
+        r <<= 8
+        if b != 0:
+            r = r | mul(a, b)
+    return r
+
+T1 = []
+T2 = []
+T3 = []
+T4 = []
+T5 = []
+T6 = []
+T7 = []
+T8 = []
+U1 = []
+U2 = []
+U3 = []
+U4 = []
+
+for t in xrange(256):
+    s = S[t]
+    T1.append(mul4(s, G[0]))
+    T2.append(mul4(s, G[1]))
+    T3.append(mul4(s, G[2]))
+    T4.append(mul4(s, G[3]))
+
+    s = Si[t]
+    T5.append(mul4(s, iG[0]))
+    T6.append(mul4(s, iG[1]))
+    T7.append(mul4(s, iG[2]))
+    T8.append(mul4(s, iG[3]))
+
+    U1.append(mul4(t, iG[0]))
+    U2.append(mul4(t, iG[1]))
+    U3.append(mul4(t, iG[2]))
+    U4.append(mul4(t, iG[3]))
+
+# round constants
+rcon = [1]
+r = 1
+for t in xrange(1, 30):
+    r = mul(2, r)
+    rcon.append(r)
+
+del A
+del AA
+del pivot
+del B
+del G
+del box
+del log
+del alog
+del i
+del j
+del r
+del s
+del t
+del mul
+del mul4
+del cox
+del iG
+
+class rijndael:
+    def __init__(self, key, block_size = 16):
+        if block_size != 16 and block_size != 24 and block_size != 32:
+            raise ValueError('Invalid block size: ' + str(block_size))
+        if len(key) != 16 and len(key) != 24 and len(key) != 32:
+            raise ValueError('Invalid key size: ' + str(len(key)))
+        self.block_size = block_size
+
+        ROUNDS = num_rounds[len(key)][block_size]
+        BC = block_size / 4
+        # encryption round keys
+        Ke = [[0] * BC for i in xrange(ROUNDS + 1)]
+        # decryption round keys
+        Kd = [[0] * BC for i in xrange(ROUNDS + 1)]
+        ROUND_KEY_COUNT = (ROUNDS + 1) * BC
+        KC = len(key) / 4
+
+        # copy user material bytes into temporary ints
+        tk = []
+        for i in xrange(0, KC):
+            tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) |
+                (ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3]))
+
+        # copy values into round key arrays
+        t = 0
+        j = 0
+        while j < KC and t < ROUND_KEY_COUNT:
+            Ke[t / BC][t % BC] = tk[j]
+            Kd[ROUNDS - (t / BC)][t % BC] = tk[j]
+            j += 1
+            t += 1
+        tt = 0
+        rconpointer = 0
+        while t < ROUND_KEY_COUNT:
+            # extrapolate using phi (the round key evolution function)
+            tt = tk[KC - 1]
+            tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^  \
+                     (S[(tt >>  8) & 0xFF] & 0xFF) << 16 ^  \
+                     (S[ tt        & 0xFF] & 0xFF) <<  8 ^  \
+                     (S[(tt >> 24) & 0xFF] & 0xFF)       ^  \
+                     (rcon[rconpointer]    & 0xFF) << 24
+            rconpointer += 1
+            if KC != 8:
+                for i in xrange(1, KC):
+                    tk[i] ^= tk[i-1]
+            else:
+                for i in xrange(1, KC / 2):
+                    tk[i] ^= tk[i-1]
+                tt = tk[KC / 2 - 1]
+                tk[KC / 2] ^= (S[ tt        & 0xFF] & 0xFF)       ^ \
+                              (S[(tt >>  8) & 0xFF] & 0xFF) <<  8 ^ \
+                              (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \
+                              (S[(tt >> 24) & 0xFF] & 0xFF) << 24
+                for i in xrange(KC / 2 + 1, KC):
+                    tk[i] ^= tk[i-1]
+            # copy values into round key arrays
+            j = 0
+            while j < KC and t < ROUND_KEY_COUNT:
+                Ke[t / BC][t % BC] = tk[j]
+                Kd[ROUNDS - (t / BC)][t % BC] = tk[j]
+                j += 1
+                t += 1
+        # inverse MixColumn where needed
+        for r in xrange(1, ROUNDS):
+            for j in xrange(BC):
+                tt = Kd[r][j]
+                Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \
+                           U2[(tt >> 16) & 0xFF] ^ \
+                           U3[(tt >>  8) & 0xFF] ^ \
+                           U4[ tt        & 0xFF]
+        self.Ke = Ke
+        self.Kd = Kd
+
+    def encrypt(self, plaintext):
+        if len(plaintext) != self.block_size:
+            raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
+        Ke = self.Ke
+
+        BC = self.block_size / 4
+        ROUNDS = len(Ke) - 1
+        if BC == 4:
+            SC = 0
+        elif BC == 6:
+            SC = 1
+        else:
+            SC = 2
+        s1 = shifts[SC][1][0]
+        s2 = shifts[SC][2][0]
+        s3 = shifts[SC][3][0]
+        a = [0] * BC
+        # temporary work array
+        t = []
+        # plaintext to ints + key
+        for i in xrange(BC):
+            t.append((ord(plaintext[i * 4    ]) << 24 |
+                      ord(plaintext[i * 4 + 1]) << 16 |
+                      ord(plaintext[i * 4 + 2]) <<  8 |
+                      ord(plaintext[i * 4 + 3])        ) ^ Ke[0][i])
+        # apply round transforms
+        for r in xrange(1, ROUNDS):
+            for i in xrange(BC):
+                a[i] = (T1[(t[ i           ] >> 24) & 0xFF] ^
+                        T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^
+                        T3[(t[(i + s2) % BC] >>  8) & 0xFF] ^
+                        T4[ t[(i + s3) % BC]        & 0xFF]  ) ^ Ke[r][i]
+            t = copy.copy(a)
+        # last round is special
+        result = []
+        for i in xrange(BC):
+            tt = Ke[ROUNDS][i]
+            result.append((S[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
+            result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
+            result.append((S[(t[(i + s2) % BC] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
+            result.append((S[ t[(i + s3) % BC]        & 0xFF] ^  tt       ) & 0xFF)
+        return string.join(map(chr, result), '')
+
+    def decrypt(self, ciphertext):
+        if len(ciphertext) != self.block_size:
+            raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
+        Kd = self.Kd
+
+        BC = self.block_size / 4
+        ROUNDS = len(Kd) - 1
+        if BC == 4:
+            SC = 0
+        elif BC == 6:
+            SC = 1
+        else:
+            SC = 2
+        s1 = shifts[SC][1][1]
+        s2 = shifts[SC][2][1]
+        s3 = shifts[SC][3][1]
+        a = [0] * BC
+        # temporary work array
+        t = [0] * BC
+        # ciphertext to ints + key
+        for i in xrange(BC):
+            t[i] = (ord(ciphertext[i * 4    ]) << 24 |
+                    ord(ciphertext[i * 4 + 1]) << 16 |
+                    ord(ciphertext[i * 4 + 2]) <<  8 |
+                    ord(ciphertext[i * 4 + 3])        ) ^ Kd[0][i]
+        # apply round transforms
+        for r in xrange(1, ROUNDS):
+            for i in xrange(BC):
+                a[i] = (T5[(t[ i           ] >> 24) & 0xFF] ^
+                        T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^
+                        T7[(t[(i + s2) % BC] >>  8) & 0xFF] ^
+                        T8[ t[(i + s3) % BC]        & 0xFF]  ) ^ Kd[r][i]
+            t = copy.copy(a)
+        # last round is special
+        result = []
+        for i in xrange(BC):
+            tt = Kd[ROUNDS][i]
+            result.append((Si[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
+            result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
+            result.append((Si[(t[(i + s2) % BC] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
+            result.append((Si[ t[(i + s3) % BC]        & 0xFF] ^  tt       ) & 0xFF)
+        return string.join(map(chr, result), '')
+
+def encrypt(key, block):
+    return rijndael(key, len(block)).encrypt(block)
+
+def decrypt(key, block):
+    return rijndael(key, len(block)).decrypt(block)
+
+def test():
+    def t(kl, bl):
+        b = 'b' * bl
+        r = rijndael('a' * kl, bl)
+        assert r.decrypt(r.encrypt(b)) == b
+    t(16, 16)
+    t(16, 24)
+    t(16, 32)
+    t(24, 16)
+    t(24, 24)
+    t(24, 32)
+    t(32, 16)
+    t(32, 24)
+    t(32, 32)
+

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/win32prng.c
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/win32prng.c	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,63 @@
+
+#include "Python.h"
+#define _WIN32_WINNT 0x0400	  /* Needed for CryptoAPI on some systems */
+#include <windows.h>
+
+
+static PyObject* getRandomBytes(PyObject *self, PyObject *args)
+{
+	int howMany;
+	HCRYPTPROV hCryptProv;
+	unsigned char* bytes = NULL;
+	PyObject* returnVal = NULL;
+
+
+	/* Read Arguments */
+    if (!PyArg_ParseTuple(args, "i", &howMany))
+    	return(NULL);
+
+	/* Get Context */
+	if(CryptAcquireContext(
+	   &hCryptProv,
+	   NULL,
+	   NULL,
+	   PROV_RSA_FULL,
+	   CRYPT_VERIFYCONTEXT) == 0)
+		return Py_BuildValue("s#", NULL, 0);
+
+
+	/* Allocate bytes */
+	bytes = malloc(howMany);
+
+
+	/* Get random data */
+	if(CryptGenRandom(
+	   hCryptProv,
+	   howMany,
+	   bytes) == 0)
+		returnVal = Py_BuildValue("s#", NULL, 0);
+	else
+		returnVal = Py_BuildValue("s#", bytes, howMany);
+
+	free(bytes);
+	CryptReleaseContext(hCryptProv, 0);
+
+	return returnVal;
+}
+
+
+
+/* List of functions exported by this module */
+
+static struct PyMethodDef win32prng_functions[] = {
+    {"getRandomBytes", (PyCFunction)getRandomBytes, METH_VARARGS},
+    {NULL,  NULL}        /* Sentinel */
+};
+
+
+/* Initialize this module. */
+
+DL_EXPORT(void) initwin32prng(void)
+{
+    Py_InitModule("win32prng", win32prng_functions);
+}

Added: trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/xmltools.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/tlslite/utils/xmltools.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,201 @@
+"""Helper functions for XML.
+
+This module has misc. helper functions for working with XML DOM nodes."""
+
+import re
+from compat import *
+
+import os
+if os.name != "java":
+    from xml.dom import minidom
+    from xml.sax import saxutils
+
+    def parseDocument(s):
+        return minidom.parseString(s)
+else:
+    from javax.xml.parsers import *
+    import java
+
+    builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
+
+    def parseDocument(s):
+        stream = java.io.ByteArrayInputStream(java.lang.String(s).getBytes())
+        return builder.parse(stream)
+
+def parseAndStripWhitespace(s):
+    try:
+        element = parseDocument(s).documentElement
+    except BaseException, e:
+        raise SyntaxError(str(e))
+    stripWhitespace(element)
+    return element
+
+#Goes through a DOM tree and removes whitespace besides child elements,
+#as long as this whitespace is correctly tab-ified
+def stripWhitespace(element, tab=0):
+    element.normalize()
+
+    lastSpacer = "\n" + ("\t"*tab)
+    spacer = lastSpacer + "\t"
+
+    #Zero children aren't allowed (i.e. <empty/>)
+    #This makes writing output simpler, and matches Canonical XML
+    if element.childNodes.length==0: #DON'T DO len(element.childNodes) - doesn't work in Jython
+        raise SyntaxError("Empty XML elements not allowed")
+
+    #If there's a single child, it must be text context
+    if element.childNodes.length==1:
+        if element.firstChild.nodeType == element.firstChild.TEXT_NODE:
+            #If it's an empty element, remove
+            if element.firstChild.data == lastSpacer:
+                element.removeChild(element.firstChild)
+            return
+        #If not text content, give an error
+        elif element.firstChild.nodeType == element.firstChild.ELEMENT_NODE:
+            raise SyntaxError("Bad whitespace under '%s'" % element.tagName)
+        else:
+            raise SyntaxError("Unexpected node type in XML document")
+
+    #Otherwise there's multiple child element
+    child = element.firstChild
+    while child:
+        if child.nodeType == child.ELEMENT_NODE:
+            stripWhitespace(child, tab+1)
+            child = child.nextSibling
+        elif child.nodeType == child.TEXT_NODE:
+            if child == element.lastChild:
+                if child.data != lastSpacer:
+                    raise SyntaxError("Bad whitespace under '%s'" % element.tagName)
+            elif child.data != spacer:
+                raise SyntaxError("Bad whitespace under '%s'" % element.tagName)
+            next = child.nextSibling
+            element.removeChild(child)
+            child = next
+        else:
+            raise SyntaxError("Unexpected node type in XML document")
+
+
+def checkName(element, name):
+    if element.nodeType != element.ELEMENT_NODE:
+        raise SyntaxError("Missing element: '%s'" % name)
+
+    if name == None:
+        return
+
+    if element.tagName != name:
+        raise SyntaxError("Wrong element name: should be '%s', is '%s'" % (name, element.tagName))
+
+def getChild(element, index, name=None):
+    if element.nodeType != element.ELEMENT_NODE:
+        raise SyntaxError("Wrong node type in getChild()")
+
+    child = element.childNodes.item(index)
+    if child == None:
+        raise SyntaxError("Missing child: '%s'" % name)
+    checkName(child, name)
+    return child
+
+def getChildIter(element, index):
+    class ChildIter:
+        def __init__(self, element, index):
+            self.element = element
+            self.index = index
+
+        def next(self):
+            if self.index < len(self.element.childNodes):
+                retVal = self.element.childNodes.item(self.index)
+                self.index += 1
+            else:
+                retVal = None
+            return retVal
+
+        def checkEnd(self):
+            if self.index != len(self.element.childNodes):
+                raise SyntaxError("Too many elements under: '%s'" % self.element.tagName)
+    return ChildIter(element, index)
+
+def getChildOrNone(element, index):
+    if element.nodeType != element.ELEMENT_NODE:
+        raise SyntaxError("Wrong node type in getChild()")
+    child = element.childNodes.item(index)
+    return child
+
+def getLastChild(element, index, name=None):
+    if element.nodeType != element.ELEMENT_NODE:
+        raise SyntaxError("Wrong node type in getLastChild()")
+
+    child = element.childNodes.item(index)
+    if child == None:
+        raise SyntaxError("Missing child: '%s'" % name)
+    if child != element.lastChild:
+        raise SyntaxError("Too many elements under: '%s'" % element.tagName)
+    checkName(child, name)
+    return child
+
+#Regular expressions for syntax-checking attribute and element content
+nsRegEx = "http://trevp.net/cryptoID\Z";
+cryptoIDRegEx = "([a-km-z3-9]{5}\.){3}[a-km-z3-9]{5}\Z"
+urlRegEx = "http(s)?://.{1,100}\Z"
+sha1Base64RegEx = "[A-Za-z0-9+/]{27}=\Z"
+base64RegEx = "[A-Za-z0-9+/]+={0,4}\Z"
+certsListRegEx = "(0)?(1)?(2)?(3)?(4)?(5)?(6)?(7)?(8)?(9)?\Z"
+keyRegEx = "[A-Z]\Z"
+keysListRegEx = "(A)?(B)?(C)?(D)?(E)?(F)?(G)?(H)?(I)?(J)?(K)?(L)?(M)?(N)?(O)?(P)?(Q)?(R)?(S)?(T)?(U)?(V)?(W)?(X)?(Y)?(Z)?\Z"
+dateTimeRegEx = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ\Z"
+shortStringRegEx = ".{1,100}\Z"
+exprRegEx = "[a-zA-Z0-9 ,()]{1,200}\Z"
+notAfterDeltaRegEx = "0|([1-9][0-9]{0,8})\Z" #A number from 0 to (1 billion)-1
+booleanRegEx = "(true)|(false)"
+
+def getReqAttribute(element, attrName, regEx=""):
+    if element.nodeType != element.ELEMENT_NODE:
+        raise SyntaxError("Wrong node type in getReqAttribute()")
+
+    value = element.getAttribute(attrName)
+    if not value:
+        raise SyntaxError("Missing Attribute: " + attrName)
+    if not re.match(regEx, value):
+        raise SyntaxError("Bad Attribute Value for '%s': '%s' " % (attrName, value))
+    element.removeAttribute(attrName)
+    return str(value) #de-unicode it; this is needed for bsddb, for example
+
+def getAttribute(element, attrName, regEx=""):
+    if element.nodeType != element.ELEMENT_NODE:
+        raise SyntaxError("Wrong node type in getAttribute()")
+
+    value = element.getAttribute(attrName)
+    if value:
+        if not re.match(regEx, value):
+            raise SyntaxError("Bad Attribute Value for '%s': '%s' " % (attrName, value))
+        element.removeAttribute(attrName)
+        return str(value) #de-unicode it; this is needed for bsddb, for example
+
+def checkNoMoreAttributes(element):
+    if element.nodeType != element.ELEMENT_NODE:
+        raise SyntaxError("Wrong node type in checkNoMoreAttributes()")
+
+    if element.attributes.length!=0:
+        raise SyntaxError("Extra attributes on '%s'" % element.tagName)
+
+def getText(element, regEx=""):
+    textNode = element.firstChild
+    if textNode == None:
+        raise SyntaxError("Empty element '%s'" % element.tagName)
+    if textNode.nodeType != textNode.TEXT_NODE:
+        raise SyntaxError("Non-text node: '%s'" % element.tagName)
+    if not re.match(regEx, textNode.data):
+        raise SyntaxError("Bad Text Value for '%s': '%s' " % (element.tagName, textNode.data))
+    return str(textNode.data) #de-unicode it; this is needed for bsddb, for example
+
+#Function for adding tabs to a string
+def indent(s, steps, ch="\t"):
+    tabs = ch*steps
+    if s[-1] != "\n":
+        s = tabs + s.replace("\n", "\n"+tabs)
+    else:
+        s = tabs + s.replace("\n", "\n"+tabs)
+        s = s[ : -len(tabs)]
+    return s
+
+def escape(s):
+    return saxutils.escape(s)

Modified: trunk/conduit/modules/GoogleModule/gdata/urlfetch.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/urlfetch.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/urlfetch.py	Tue Mar 17 09:00:35 2009
@@ -15,7 +15,17 @@
 # limitations under the License.
 
 
-"""Provides HttpRequest function for gdata.service to use on Google App Engine
+"""Provides HTTP functions for gdata.service to use on Google App Engine
+
+AppEngineHttpClient: Provides an HTTP request method which uses App Engine's
+   urlfetch API. Set the http_client member of a GDataService object to an
+   instance of an AppEngineHttpClient to allow the gdata library to run on
+   Google App Engine.
+
+run_on_appengine: Function which will modify an existing GDataService object
+   to allow it to run on App Engine. It works by creating a new instance of
+   the AppEngineHttpClient and replacing the GDataService object's 
+   http_client.
 
 HttpRequest: Function that wraps google.appengine.api.urlfetch.Fetch in a 
     common interface which is used by gdata.service.GDataService. In other 
@@ -25,18 +35,99 @@
 """
 
 
-__author__ = 'api.jscudder (Jeffrey Scudder)'
+__author__ = 'api.jscudder (Jeff Scudder)'
 
 
 import StringIO
 import atom.service
+import atom.http_interface
 from google.appengine.api import urlfetch
 
 
+def run_on_appengine(gdata_service):
+  """Modifies a GDataService object to allow it to run on App Engine.
+
+  Args:
+    gdata_service: An instance of AtomService, GDataService, or any
+        of their subclasses which has an http_client member.
+  """
+  gdata_service.http_client = AppEngineHttpClient()
+
+
+class AppEngineHttpClient(atom.http_interface.GenericHttpClient):
+  def __init__(self, headers=None):
+    self.debug = False
+    self.headers = headers or {}
+
+  def request(self, operation, url, data=None, headers=None):
+    """Performs an HTTP call to the server, supports GET, POST, PUT, and
+    DELETE.
+
+    Usage example, perform and HTTP GET on http://www.google.com/:
+      import atom.http
+      client = atom.http.HttpClient()
+      http_response = client.request('GET', 'http://www.google.com/')
+
+    Args:
+      operation: str The HTTP operation to be performed. This is usually one
+          of 'GET', 'POST', 'PUT', or 'DELETE'
+      data: filestream, list of parts, or other object which can be converted
+          to a string. Should be set to None when performing a GET or DELETE.
+          If data is a file-like object which can be read, this method will
+          read a chunk of 100K bytes at a time and send them.
+          If the data is a list of parts to be sent, each part will be
+          evaluated and sent.
+      url: The full URL to which the request should be sent. Can be a string
+          or atom.url.Url.
+      headers: dict of strings. HTTP headers which should be sent
+          in the request.
+    """
+    all_headers = self.headers.copy()
+    if headers:
+      all_headers.update(headers)
+
+    # Construct the full payload.
+    # Assume that data is None or a string.
+    data_str = data
+    if data:
+      if isinstance(data, list):
+        # If data is a list of different objects, convert them all to strings
+        # and join them together.
+        converted_parts = [__ConvertDataPart(x) for x in data]
+        data_str = ''.join(converted_parts)
+      else:
+        data_str = __ConvertDataPart(data)
+
+    # If the list of headers does not include a Content-Length, attempt to
+    # calculate it based on the data object.
+    if data and 'Content-Length' not in all_headers:
+      all_headers['Content-Length'] = len(data_str)
+
+    # Set the content type to the default value if none was set.
+    if 'Content-Type' not in all_headers:
+      all_headers['Content-Type'] = 'application/atom+xml'
+
+    # Lookup the urlfetch operation which corresponds to the desired HTTP verb.
+    if operation == 'GET':
+      method = urlfetch.GET
+    elif operation == 'POST':
+      method = urlfetch.POST
+    elif operation == 'PUT':
+      method = urlfetch.PUT
+    elif operation == 'DELETE':
+      method = urlfetch.DELETE
+    else:
+      method = None
+    return HttpResponse(urlfetch.Fetch(url=str(url), payload=data_str,
+        method=method, headers=all_headers))
+ 
+
 def HttpRequest(service, operation, data, uri, extra_headers=None,
     url_params=None, escape_params=True, content_type='application/atom+xml'):
   """Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
 
+  This function is deprecated, use AppEngineHttpClient.request instead.
+
   To use this module with gdata.service, you can set this module to be the
   http_request_handler so that HTTP requests use Google App Engine's urlfetch.
   import gdata.service

Added: trunk/conduit/modules/GoogleModule/gdata/webmastertools/__init__.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/webmastertools/__init__.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,542 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Yu-Jie Lin
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Contains extensions to Atom objects used with Google Webmaster Tools."""
+
+
+__author__ = 'livibetter (Yu-Jie Lin)'
+
+
+try:
+  from xml.etree import cElementTree as ElementTree
+except ImportError:
+  try:
+    import cElementTree as ElementTree
+  except ImportError:
+    try:
+      from xml.etree import ElementTree
+    except ImportError:
+      from elementtree import ElementTree
+import atom
+import gdata
+
+
+# XML namespaces which are often used in Google Webmaster Tools entities.
+GWEBMASTERTOOLS_NAMESPACE = 'http://schemas.google.com/webmasters/tools/2007'
+GWEBMASTERTOOLS_TEMPLATE = '{http://schemas.google.com/webmasters/tools/2007}%s'
+
+
+class Indexed(atom.AtomBase):
+  _tag = 'indexed'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def IndexedFromString(xml_string):
+  return atom.CreateClassFromXMLString(Indexed, xml_string)
+
+
+class Crawled(atom.Date):
+  _tag = 'crawled'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def CrawledFromString(xml_string):
+  return atom.CreateClassFromXMLString(Crawled, xml_string)
+
+
+class GeoLocation(atom.AtomBase):
+  _tag = 'geolocation'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def GeoLocationFromString(xml_string):
+  return atom.CreateClassFromXMLString(GeoLocation, xml_string)
+
+
+class PreferredDomain(atom.AtomBase):
+  _tag = 'preferred-domain'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def PreferredDomainFromString(xml_string):
+  return atom.CreateClassFromXMLString(PreferredDomain, xml_string)
+
+
+class CrawlRate(atom.AtomBase):
+  _tag = 'crawl-rate'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def CrawlRateFromString(xml_string):
+  return atom.CreateClassFromXMLString(CrawlRate, xml_string)
+
+
+class EnhancedImageSearch(atom.AtomBase):
+  _tag = 'enhanced-image-search'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def EnhancedImageSearchFromString(xml_string):
+  return atom.CreateClassFromXMLString(EnhancedImageSearch, xml_string)
+
+
+class Verified(atom.AtomBase):
+  _tag = 'verified'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def VerifiedFromString(xml_string):
+  return atom.CreateClassFromXMLString(Verified, xml_string)
+
+
+class VerificationMethodMeta(atom.AtomBase):
+  _tag = 'meta'
+  _namespace = atom.ATOM_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _attributes['name'] = 'name'
+  _attributes['content'] = 'content'
+
+  def __init__(self, text=None, name=None, content=None,
+      extension_elements=None, extension_attributes=None):
+    self.text = text
+    self.name = name
+    self.content = content
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def VerificationMethodMetaFromString(xml_string):
+  return atom.CreateClassFromXMLString(VerificationMethodMeta, xml_string)
+
+
+class VerificationMethod(atom.AtomBase):
+  _tag = 'verification-method'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+  _children = atom.Text._children.copy()
+  _attributes = atom.Text._attributes.copy()
+  _children['{%s}meta' % atom.ATOM_NAMESPACE] = (
+    'meta', VerificationMethodMeta)
+  _attributes['in-use'] = 'in_use'
+
+  def __init__(self, text=None, in_use=None, meta=None,
+      extension_elements=None, extension_attributes=None):
+    self.text = text
+    self.in_use = in_use
+    self.meta = meta
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def VerificationMethodFromString(xml_string):
+  return atom.CreateClassFromXMLString(VerificationMethod, xml_string)
+
+
+class MarkupLanguage(atom.AtomBase):
+  _tag = 'markup-language'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def MarkupLanguageFromString(xml_string):
+  return atom.CreateClassFromXMLString(MarkupLanguage, xml_string)
+
+
+class SitemapMobile(atom.AtomBase):
+  _tag = 'sitemap-mobile'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _children['{%s}markup-language' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'markup_language', [MarkupLanguage])
+
+  def __init__(self, markup_language=None,
+      extension_elements=None, extension_attributes=None, text=None):
+
+    self.markup_language = markup_language or []
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def SitemapMobileFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapMobile, xml_string)
+
+
+class SitemapMobileMarkupLanguage(atom.AtomBase):
+  _tag = 'sitemap-mobile-markup-language'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def SitemapMobileMarkupLanguageFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapMobileMarkupLanguage, xml_string)
+
+
+class PublicationLabel(atom.AtomBase):
+  _tag = 'publication-label'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def PublicationLabelFromString(xml_string):
+  return atom.CreateClassFromXMLString(PublicationLabel, xml_string)
+
+
+class SitemapNews(atom.AtomBase):
+  _tag = 'sitemap-news'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+  _children = atom.AtomBase._children.copy()
+  _attributes = atom.AtomBase._attributes.copy()
+  _children['{%s}publication-label' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'publication_label', [PublicationLabel])
+
+  def __init__(self, publication_label=None,
+    extension_elements=None, extension_attributes=None, text=None):
+
+    self.publication_label = publication_label or []
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def SitemapNewsFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapNews, xml_string)
+
+
+class SitemapNewsPublicationLabel(atom.AtomBase):
+  _tag = 'sitemap-news-publication-label'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def SitemapNewsPublicationLabelFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapNewsPublicationLabel, xml_string)
+
+
+class SitemapLastDownloaded(atom.Date):
+  _tag = 'sitemap-last-downloaded'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def SitemapLastDownloadedFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapLastDownloaded, xml_string)
+
+
+class SitemapType(atom.AtomBase):
+  _tag = 'sitemap-type'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def SitemapTypeFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapType, xml_string)
+
+
+class SitemapStatus(atom.AtomBase):
+  _tag = 'sitemap-status'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def SitemapStatusFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapStatus, xml_string)
+
+
+class SitemapUrlCount(atom.AtomBase):
+  _tag = 'sitemap-url-count'
+  _namespace = GWEBMASTERTOOLS_NAMESPACE
+
+
+def SitemapUrlCountFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapUrlCount, xml_string)
+
+
+class LinkFinder(atom.LinkFinder):
+  """An "interface" providing methods to find link elements
+
+  SitesEntry elements often contain multiple links which differ in the rel 
+  attribute or content type. Often, developers are interested in a specific
+  type of link so this class provides methods to find specific classes of links.
+
+  This class is used as a mixin in SitesEntry.
+  """
+
+  def GetSelfLink(self):
+    """Find the first link with rel set to 'self'
+
+    Returns:
+      An atom.Link or none if none of the links had rel equal to 'self'
+    """
+
+    for a_link in self.link:
+      if a_link.rel == 'self':
+        return a_link
+    return None
+
+  def GetEditLink(self):
+    for a_link in self.link:
+      if a_link.rel == 'edit':
+        return a_link
+    return None
+
+  def GetPostLink(self):
+    """Get a link containing the POST target URL.
+    
+    The POST target URL is used to insert new entries.
+
+    Returns:
+      A link object with a rel matching the POST type.
+    """
+    for a_link in self.link:
+      if a_link.rel == 'http://schemas.google.com/g/2005#post':
+        return a_link
+    return None
+
+  def GetFeedLink(self):
+    for a_link in self.link:
+      if a_link.rel == 'http://schemas.google.com/g/2005#feed':
+        return a_link
+    return None
+
+
+class SitesEntry(atom.Entry, LinkFinder):
+  """A Google Webmaster Tools meta Entry flavor of an Atom Entry """
+
+  _tag = atom.Entry._tag
+  _namespace = atom.Entry._namespace
+  _children = atom.Entry._children.copy()
+  _attributes = atom.Entry._attributes.copy()
+  _children['{%s}entryLink' % gdata.GDATA_NAMESPACE] = (
+      'entry_link', [gdata.EntryLink])
+  _children['{%s}indexed' % GWEBMASTERTOOLS_NAMESPACE] = ('indexed', Indexed)
+  _children['{%s}crawled' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'crawled', Crawled)
+  _children['{%s}geolocation' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'geolocation', GeoLocation)
+  _children['{%s}preferred-domain' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'preferred_domain', PreferredDomain)
+  _children['{%s}crawl-rate' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'crawl_rate', CrawlRate)
+  _children['{%s}enhanced-image-search' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'enhanced_image_search', EnhancedImageSearch)
+  _children['{%s}verified' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'verified', Verified)
+  _children['{%s}verification-method' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'verification_method', [VerificationMethod])
+  
+  def __GetId(self):
+    return self.__id
+
+  # This method was created to strip the unwanted whitespace from the id's 
+  # text node.
+  def __SetId(self, id):
+    self.__id = id
+    if id is not None and id.text is not None:
+      self.__id.text = id.text.strip()
+
+  id = property(__GetId, __SetId)
+
+  def __init__(self, category=None, content=None,
+      atom_id=None, link=None, title=None, updated=None,
+      entry_link=None, indexed=None, crawled=None,
+      geolocation=None, preferred_domain=None, crawl_rate=None,
+      enhanced_image_search=None,
+      verified=None, verification_method=None,
+      extension_elements=None, extension_attributes=None, text=None):
+    atom.Entry.__init__(self, category=category, 
+                        content=content, atom_id=atom_id, link=link, 
+                        title=title, updated=updated, text=text)
+
+    self.entry_link = entry_link or []
+    self.indexed = indexed
+    self.crawled = crawled
+    self.geolocation = geolocation
+    self.preferred_domain = preferred_domain
+    self.crawl_rate = crawl_rate
+    self.enhanced_image_search = enhanced_image_search
+    self.verified = verified
+    self.verification_method = verification_method or []
+
+
+def SitesEntryFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitesEntry, xml_string)
+
+
+class SitesFeed(atom.Feed, LinkFinder):
+  """A Google Webmaster Tools meta Sites feed flavor of an Atom Feed"""
+
+  _tag = atom.Feed._tag
+  _namespace = atom.Feed._namespace
+  _children = atom.Feed._children.copy()
+  _attributes = atom.Feed._attributes.copy()
+  _children['{%s}startIndex' % gdata.OPENSEARCH_NAMESPACE] = (
+      'start_index', gdata.StartIndex)
+  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SitesEntry])
+  del _children['{%s}generator' % atom.ATOM_NAMESPACE]
+  del _children['{%s}author' % atom.ATOM_NAMESPACE]
+  del _children['{%s}contributor' % atom.ATOM_NAMESPACE]
+  del _children['{%s}logo' % atom.ATOM_NAMESPACE]
+  del _children['{%s}icon' % atom.ATOM_NAMESPACE]
+  del _children['{%s}rights' % atom.ATOM_NAMESPACE]
+  del _children['{%s}subtitle' % atom.ATOM_NAMESPACE]
+
+  def __GetId(self):
+    return self.__id
+
+  def __SetId(self, id):
+    self.__id = id
+    if id is not None and id.text is not None:
+      self.__id.text = id.text.strip()
+
+  id = property(__GetId, __SetId)
+
+  def __init__(self, start_index=None, atom_id=None, title=None, entry=None,
+      category=None, link=None, updated=None,
+      extension_elements=None, extension_attributes=None, text=None):
+    """Constructor for Source
+    
+    Args:
+      category: list (optional) A list of Category instances
+      id: Id (optional) The entry's Id element
+      link: list (optional) A list of Link instances
+      title: Title (optional) the entry's title element
+      updated: Updated (optional) the entry's updated element
+      entry: list (optional) A list of the Entry instances contained in the 
+          feed.
+      text: String (optional) The text contents of the element. This is the 
+          contents of the Entry's XML text node. 
+          (Example: <foo>This is the text</foo>)
+      extension_elements: list (optional) A list of ExtensionElement instances
+          which are children of this element.
+      extension_attributes: dict (optional) A dictionary of strings which are 
+          the values for additional XML attributes of this element.
+    """
+
+    self.start_index = start_index
+    self.category = category or []
+    self.id = atom_id
+    self.link = link or []
+    self.title = title
+    self.updated = updated
+    self.entry = entry or []
+    self.text = text
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def SitesFeedFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitesFeed, xml_string)
+
+
+class SitemapsEntry(atom.Entry, LinkFinder):
+  """A Google Webmaster Tools meta Sitemaps Entry flavor of an Atom Entry """
+
+  _tag = atom.Entry._tag
+  _namespace = atom.Entry._namespace
+  _children = atom.Entry._children.copy()
+  _attributes = atom.Entry._attributes.copy()
+  _children['{%s}sitemap-type' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'sitemap_type', SitemapType)
+  _children['{%s}sitemap-status' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'sitemap_status', SitemapStatus)
+  _children['{%s}sitemap-last-downloaded' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'sitemap_last_downloaded', SitemapLastDownloaded)
+  _children['{%s}sitemap-url-count' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'sitemap_url_count', SitemapUrlCount)
+  _children['{%s}sitemap-mobile-markup-language' % GWEBMASTERTOOLS_NAMESPACE] \
+      = ('sitemap_mobile_markup_language', SitemapMobileMarkupLanguage)
+  _children['{%s}sitemap-news-publication-label' % GWEBMASTERTOOLS_NAMESPACE] \
+      = ('sitemap_news_publication_label', SitemapNewsPublicationLabel)
+  
+  def __GetId(self):
+    return self.__id
+
+  # This method was created to strip the unwanted whitespace from the id's 
+  # text node.
+  def __SetId(self, id):
+    self.__id = id
+    if id is not None and id.text is not None:
+      self.__id.text = id.text.strip()
+
+  id = property(__GetId, __SetId)
+
+  def __init__(self, category=None, content=None,
+      atom_id=None, link=None, title=None, updated=None,
+      sitemap_type=None, sitemap_status=None, sitemap_last_downloaded=None,
+      sitemap_url_count=None, sitemap_mobile_markup_language=None,
+      sitemap_news_publication_label=None,
+      extension_elements=None, extension_attributes=None, text=None):
+    atom.Entry.__init__(self, category=category, 
+                        content=content, atom_id=atom_id, link=link, 
+                        title=title, updated=updated, text=text)
+
+    self.sitemap_type = sitemap_type
+    self.sitemap_status = sitemap_status
+    self.sitemap_last_downloaded = sitemap_last_downloaded
+    self.sitemap_url_count = sitemap_url_count
+    self.sitemap_mobile_markup_language = sitemap_mobile_markup_language
+    self.sitemap_news_publication_label = sitemap_news_publication_label
+
+
+def SitemapsEntryFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapsEntry, xml_string)
+
+
+class SitemapsFeed(atom.Feed, LinkFinder):
+  """A Google Webmaster Tools meta Sitemaps feed flavor of an Atom Feed"""
+
+  _tag = atom.Feed._tag
+  _namespace = atom.Feed._namespace
+  _children = atom.Feed._children.copy()
+  _attributes = atom.Feed._attributes.copy()
+  _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [SitemapsEntry])
+  _children['{%s}sitemap-mobile' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'sitemap_mobile', SitemapMobile)
+  _children['{%s}sitemap-news' % GWEBMASTERTOOLS_NAMESPACE] = (
+      'sitemap_news', SitemapNews)
+  del _children['{%s}generator' % atom.ATOM_NAMESPACE]
+  del _children['{%s}author' % atom.ATOM_NAMESPACE]
+  del _children['{%s}contributor' % atom.ATOM_NAMESPACE]
+  del _children['{%s}logo' % atom.ATOM_NAMESPACE]
+  del _children['{%s}icon' % atom.ATOM_NAMESPACE]
+  del _children['{%s}rights' % atom.ATOM_NAMESPACE]
+  del _children['{%s}subtitle' % atom.ATOM_NAMESPACE]
+
+  def __GetId(self):
+    return self.__id
+
+  def __SetId(self, id):
+    self.__id = id
+    if id is not None and id.text is not None:
+      self.__id.text = id.text.strip()
+
+  id = property(__GetId, __SetId)
+
+  def __init__(self, category=None, content=None,
+      atom_id=None, link=None, title=None, updated=None,
+      entry=None, sitemap_mobile=None, sitemap_news=None,
+      extension_elements=None, extension_attributes=None, text=None):
+
+    self.category = category or []
+    self.id = atom_id
+    self.link = link or []
+    self.title = title
+    self.updated = updated
+    self.entry = entry or []
+    self.text = text
+    self.sitemap_mobile = sitemap_mobile
+    self.sitemap_news = sitemap_news
+    self.extension_elements = extension_elements or []
+    self.extension_attributes = extension_attributes or {}
+
+
+def SitemapsFeedFromString(xml_string):
+  return atom.CreateClassFromXMLString(SitemapsFeed, xml_string)

Added: trunk/conduit/modules/GoogleModule/gdata/webmastertools/service.py
==============================================================================
--- (empty file)
+++ trunk/conduit/modules/GoogleModule/gdata/webmastertools/service.py	Tue Mar 17 09:00:35 2009
@@ -0,0 +1,516 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2008 Yu-Jie Lin
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""GWebmasterToolsService extends the GDataService to streamline
+Google Webmaster Tools operations.
+
+  GWebmasterToolsService: Provides methods to query feeds and manipulate items.
+                          Extends GDataService.
+"""
+
+__author__ = 'livibetter (Yu-Jie Lin)'
+
+import urllib
+import gdata
+import atom.service
+import gdata.service
+import gdata.webmastertools as webmastertools
+import atom
+
+
+FEED_BASE = 'https://www.google.com/webmasters/tools/feeds/'
+SITES_FEED = FEED_BASE + 'sites/'
+SITE_TEMPLATE = SITES_FEED + '%s'
+SITEMAPS_FEED_TEMPLATE = FEED_BASE + '%(site_id)s/sitemaps/'
+SITEMAP_TEMPLATE = SITEMAPS_FEED_TEMPLATE + '%(sitemap_id)s'
+
+
+class Error(Exception):
+  pass
+
+
+class RequestError(Error):
+  pass
+
+
+class GWebmasterToolsService(gdata.service.GDataService):
+  """Client for the Google Webmaster Tools service."""
+
+  def __init__(self, email=None, password=None, source=None,
+               server='www.google.com', **kwargs):
+    """Creates a client for the Google Webmaster Tools service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'www.google.com'.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service='sitemaps', source=source,
+        server=server, **kwargs)
+
+  def GetSitesFeed(self, uri=SITES_FEED,
+      converter=webmastertools.SitesFeedFromString):
+    """Gets sites feed.
+
+    Args:
+      uri: str (optional) URI to retrieve sites feed.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitesFeedFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitesFeed object.
+    """
+    return self.Get(uri, converter=converter)
+
+  def AddSite(self, site_uri, uri=SITES_FEED,
+      url_params=None, escape_params=True, converter=None):
+    """Adds a site to Google Webmaster Tools.
+
+    Args: 
+      site_uri: str URI of which site to add.
+      uri: str (optional) URI to add a site.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitesEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitesEntry object.
+    """
+
+    site_entry = webmastertools.SitesEntry()
+    site_entry.content = atom.Content(src=site_uri)
+    response = self.Post(site_entry, uri,
+        url_params=url_params, 
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitesEntryFromString(response.ToString())
+    return response
+
+  def DeleteSite(self, site_uri, uri=SITE_TEMPLATE,
+      url_params=None, escape_params=True):
+    """Removes a site from Google Webmaster Tools.
+
+    Args: 
+      site_uri: str URI of which site to remove.
+      uri: str (optional) A URI template to send DELETE request.
+           Default SITE_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+
+    Returns:
+      True if the delete succeeded.
+    """
+
+    return self.Delete(
+        uri % urllib.quote_plus(site_uri),
+        url_params=url_params, escape_params=escape_params)
+
+  def VerifySite(self, site_uri, verification_method, uri=SITE_TEMPLATE,
+      url_params=None, escape_params=True, converter=None):
+    """Requests a verification of a site.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      verification_method: str The method to verify a site. Valid values are
+                           'htmlpage', and 'metatag'.
+      uri: str (optional) URI template to update a site.
+           Default SITE_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitesEntry object.
+    """
+
+    site_entry = webmastertools.SitesEntry(
+        atom_id=atom.Id(text=site_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sites-info'),
+        verification_method=webmastertools.VerificationMethod(
+            type=verification_method, in_user='true')
+        )
+    response = self.Put(
+        site_entry,
+        uri % urllib.quote_plus(site_uri),
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitesEntryFromString(response.ToString())
+    return response
+
+
+  def UpdateGeoLocation(self, site_uri, geolocation, uri=SITE_TEMPLATE,
+      url_params=None, escape_params=True, converter=None):
+    """Updates geolocation setting of a site.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      geolocation: str The geographic location. Valid values are listed in
+                   http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+      uri: str (optional) URI template to update a site.
+           Default SITE_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitesEntry object.
+    """
+
+    site_entry = webmastertools.SitesEntry(
+        atom_id=atom.Id(text=site_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sites-info'),
+        geolocation=webmastertools.GeoLocation(text=geolocation)
+        )
+    response = self.Put(
+        site_entry,
+        uri % urllib.quote_plus(site_uri),
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitesEntryFromString(response.ToString())
+    return response
+
+  def UpdateCrawlRate(self, site_uri, crawl_rate, uri=SITE_TEMPLATE,
+      url_params=None, escape_params=True, converter=None):
+    """Updates crawl rate setting of a site.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      crawl_rate: str The crawl rate for a site. Valid values are 'slower',
+                  'normal', and 'faster'.
+      uri: str (optional) URI template to update a site.
+           Default SITE_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitesEntry object.
+    """
+
+    site_entry = webmastertools.SitesEntry(
+        atom_id=atom.Id(text=site_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sites-info'),
+        crawl_rate=webmastertools.CrawlRate(text=crawl_rate)
+        )
+    response = self.Put(
+        site_entry,
+        uri % urllib.quote_plus(site_uri),
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitesEntryFromString(response.ToString())
+    return response
+
+  def UpdatePreferredDomain(self, site_uri, preferred_domain, uri=SITE_TEMPLATE,
+      url_params=None, escape_params=True, converter=None):
+    """Updates preferred domain setting of a site.
+
+    Note that if using 'preferwww', will also need www.example.com in account to
+    take effect.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      preferred_domain: str The preferred domain for a site. Valid values are 'none',
+                        'preferwww', and 'prefernowww'.
+      uri: str (optional) URI template to update a site.
+           Default SITE_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitesEntry object.
+    """
+
+    site_entry = webmastertools.SitesEntry(
+        atom_id=atom.Id(text=site_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sites-info'),
+        preferred_domain=webmastertools.PreferredDomain(text=preferred_domain)
+        )
+    response = self.Put(
+        site_entry,
+        uri % urllib.quote_plus(site_uri),
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitesEntryFromString(response.ToString())
+    return response
+
+  def UpdateEnhancedImageSearch(self, site_uri, enhanced_image_search,
+      uri=SITE_TEMPLATE, url_params=None, escape_params=True, converter=None):
+    """Updates enhanced image search setting of a site.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      enhanced_image_search: str The enhanced image search setting for a site.
+                             Valid values are 'true', and 'false'.
+      uri: str (optional) URI template to update a site.
+           Default SITE_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitesEntry object.
+    """
+
+    site_entry = webmastertools.SitesEntry(
+        atom_id=atom.Id(text=site_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sites-info'),
+        enhanced_image_search=webmastertools.EnhancedImageSearch(
+            text=enhanced_image_search)
+        )
+    response = self.Put(
+        site_entry,
+        uri % urllib.quote_plus(site_uri),
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitesEntryFromString(response.ToString())
+    return response
+
+  def GetSitemapsFeed(self, site_uri, uri=SITEMAPS_FEED_TEMPLATE,
+      converter=webmastertools.SitemapsFeedFromString):
+    """Gets sitemaps feed of a site.
+    
+    Args:
+      site_uri: str (optional) URI of which site to retrieve its sitemaps feed.
+      uri: str (optional) URI to retrieve sites feed.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsFeedFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitemapsFeed object.
+    """
+    return self.Get(uri % {'site_id': urllib.quote_plus(site_uri)},
+        converter=converter)
+
+  def AddSitemap(self, site_uri, sitemap_uri, sitemap_type='WEB',
+      uri=SITEMAPS_FEED_TEMPLATE,
+      url_params=None, escape_params=True, converter=None):
+    """Adds a regular sitemap to a site.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      sitemap_uri: str URI of sitemap to add to a site.
+      sitemap_type: str Type of added sitemap. Valid types: WEB, VIDEO, or CODE.
+      uri: str (optional) URI template to add a sitemap.
+           Default SITEMAP_FEED_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitemapsEntry object.
+    """
+
+    sitemap_entry = webmastertools.SitemapsEntry(
+        atom_id=atom.Id(text=sitemap_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sitemap-regular'),
+        sitemap_type=webmastertools.SitemapType(text=sitemap_type))
+    response = self.Post(
+        sitemap_entry,
+        uri % {'site_id': urllib.quote_plus(site_uri)},
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitemapsEntryFromString(response.ToString())
+    return response
+
+  def AddMobileSitemap(self, site_uri, sitemap_uri,
+      sitemap_mobile_markup_language='XHTML', uri=SITEMAPS_FEED_TEMPLATE,
+      url_params=None, escape_params=True, converter=None):
+    """Adds a mobile sitemap to a site.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      sitemap_uri: str URI of sitemap to add to a site.
+      sitemap_mobile_markup_language: str Format of added sitemap. Valid types:
+                                      XHTML, WML, or cHTML.
+      uri: str (optional) URI template to add a sitemap.
+           Default SITEMAP_FEED_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitemapsEntry object.
+    """
+    # FIXME
+    sitemap_entry = webmastertools.SitemapsEntry(
+        atom_id=atom.Id(text=sitemap_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sitemap-mobile'),
+        sitemap_mobile_markup_language=\
+            webmastertools.SitemapMobileMarkupLanguage(
+                text=sitemap_mobile_markup_language))
+    print sitemap_entry
+    response = self.Post(
+        sitemap_entry,
+        uri % {'site_id': urllib.quote_plus(site_uri)},
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitemapsEntryFromString(response.ToString())
+    return response
+
+  def AddNewsSitemap(self, site_uri, sitemap_uri,
+      sitemap_news_publication_label, uri=SITEMAPS_FEED_TEMPLATE,
+      url_params=None, escape_params=True, converter=None):
+    """Adds a news sitemap to a site.
+
+    Args: 
+      site_uri: str URI of which site to add sitemap for.
+      sitemap_uri: str URI of sitemap to add to a site.
+      sitemap_news_publication_label: str, list of str Publication Labels for
+                                      sitemap.
+      uri: str (optional) URI template to add a sitemap.
+           Default SITEMAP_FEED_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+      converter: func (optional) Function which is executed on the server's
+          response before it is returned. Usually this is a function like
+          SitemapsEntryFromString which will parse the response and turn it into
+          an object.
+
+    Returns:
+      If converter is defined, the results of running converter on the server's
+      response. Otherwise, it will be a SitemapsEntry object.
+    """
+
+    sitemap_entry = webmastertools.SitemapsEntry(
+        atom_id=atom.Id(text=sitemap_uri),
+        category=atom.Category(
+            scheme='http://schemas.google.com/g/2005#kind',
+            term='http://schemas.google.com/webmasters/tools/2007#sitemap-news'),
+        sitemap_news_publication_label=[],
+        )
+    if isinstance(sitemap_news_publication_label, str):
+      sitemap_news_publication_label = [sitemap_news_publication_label]
+    for label in sitemap_news_publication_label:
+      sitemap_entry.sitemap_news_publication_label.append(
+          webmastertools.SitemapNewsPublicationLabel(text=label))
+    print sitemap_entry
+    response = self.Post(
+        sitemap_entry,
+        uri % {'site_id': urllib.quote_plus(site_uri)},
+        url_params=url_params,
+        escape_params=escape_params, converter=converter)
+    if not converter and isinstance(response, atom.Entry):
+      return webmastertools.SitemapsEntryFromString(response.ToString())
+    return response
+
+  def DeleteSitemap(self, site_uri, sitemap_uri, uri=SITEMAP_TEMPLATE,
+      url_params=None, escape_params=True):
+    """Removes a sitemap from a site.
+
+    Args: 
+      site_uri: str URI of which site to remove a sitemap from.
+      sitemap_uri: str URI of sitemap to remove from a site.
+      uri: str (optional) A URI template to send DELETE request.
+           Default SITEMAP_TEMPLATE.
+      url_params: dict (optional) Additional URL parameters to be included
+                  in the insertion request. 
+      escape_params: boolean (optional) If true, the url_parameters will be
+                     escaped before they are included in the request.
+
+    Returns:
+      True if the delete succeeded.
+    """
+
+    return self.Delete(
+        uri % {'site_id': urllib.quote_plus(site_uri),
+            'sitemap_id': urllib.quote_plus(sitemap_uri)},
+        url_params=url_params, escape_params=escape_params)

Modified: trunk/conduit/modules/GoogleModule/gdata/youtube/__init__.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/youtube/__init__.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/youtube/__init__.py	Tue Mar 17 09:00:35 2009
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 #
-# Copyright (C) 2006 Google Inc.
+# Copyright (C) 2008 Google Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 __author__ = ('api stephaniel gmail com (Stephanie Liu)'
               ', api jhartmann gmail com (Jochen Hartmann)')
 
@@ -23,16 +22,23 @@
 import gdata.media as Media
 import gdata.geo as Geo
 
-# XML namespaces which are often used in YouTube entities.
 YOUTUBE_NAMESPACE = 'http://gdata.youtube.com/schemas/2007'
-YOUTUBE_TEMPLATE = '{http://gdata.youtube.com/schemas/2007}%s'
 YOUTUBE_FORMAT = '{http://gdata.youtube.com/schemas/2007}format'
+YOUTUBE_DEVELOPER_TAG_SCHEME = '%s/%s' % (YOUTUBE_NAMESPACE,
+                                          'developertags.cat')
+YOUTUBE_SUBSCRIPTION_TYPE_SCHEME = '%s/%s' % (YOUTUBE_NAMESPACE,
+                                              'subscriptiontypes.cat')
 
 class Username(atom.AtomBase):
   """The YouTube Username element"""
   _tag = 'username'
   _namespace = YOUTUBE_NAMESPACE
 
+class QueryString(atom.AtomBase):
+  """The YouTube QueryString element"""
+  _tag = 'queryString'
+  _namespace = YOUTUBE_NAMESPACE
+
 
 class FirstName(atom.AtomBase):
   """The YouTube FirstName element"""
@@ -125,7 +131,7 @@
 
 
 class Statistics(atom.AtomBase):
-  """The YouTube Statistics element"""
+  """The YouTube Statistics element."""
   _tag = 'statistics'
   _namespace = YOUTUBE_NAMESPACE
   _attributes = atom.AtomBase._attributes.copy() 
@@ -225,6 +231,7 @@
 
 
 class YouTubePlaylistVideoEntry(gdata.GDataEntry):
+  """Represents a YouTubeVideoEntry on a YouTubePlaylist."""
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
@@ -265,6 +272,7 @@
 
 
 class YouTubeVideoCommentEntry(gdata.GDataEntry):
+  """Represents a comment on YouTube."""
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
@@ -272,17 +280,20 @@
 
 
 class YouTubeSubscriptionEntry(gdata.GDataEntry):
+  """Represents a subscription entry on YouTube."""
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
   _attributes = gdata.GDataEntry._attributes.copy()
   _children['{%s}username' % YOUTUBE_NAMESPACE] = ('username', Username)
+  _children['{%s}queryString' % YOUTUBE_NAMESPACE] = (
+      'query_string', QueryString)
   _children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
                                                         [gdata.FeedLink])
 
   def __init__(self, author=None, category=None, content=None,
                atom_id=None, link=None, published=None, title=None,
-               updated=None, username=None, feed_link=None,
+               updated=None, username=None, query_string=None, feed_link=None,
                extension_elements=None, extension_attributes=None):
 
     gdata.GDataEntry.__init__(self, author=author, category=category,
@@ -290,10 +301,23 @@
                               published=published, title=title, updated=updated)
 
     self.username = username
+    self.query_string = query_string
     self.feed_link = feed_link
 
 
+  def GetSubscriptionType(self):
+    """Retrieve the type of this subscription.
+
+    Returns:
+      A string that is either 'channel, 'query' or 'favorites'
+    """
+    for category in self.category:
+      if category.scheme == YOUTUBE_SUBSCRIPTION_TYPE_SCHEME:
+        return category.term
+
+
 class YouTubeVideoResponseEntry(gdata.GDataEntry):
+  """Represents a video response. """
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
@@ -321,6 +345,7 @@
 
 
 class YouTubeContactEntry(gdata.GDataEntry):
+  """Represents a contact entry."""
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
@@ -343,6 +368,7 @@
 
 
 class YouTubeVideoEntry(gdata.GDataEntry):
+  """Represents a video on YouTube."""
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
@@ -390,8 +416,45 @@
     else:
       return None
 
+  def AddDeveloperTags(self, developer_tags):
+    """Add a developer tag for this entry.
+
+    Developer tags can only be set during the initial upload.
+
+    Arguments:
+      developer_tags: A list of developer tags as strings.
+
+    Returns:
+      A list of all developer tags for this video entry.
+    """
+    for tag_text in developer_tags:
+      self.media.category.append(gdata.media.Category(
+          text=tag_text, label=tag_text, scheme=YOUTUBE_DEVELOPER_TAG_SCHEME))
+
+    return self.GetDeveloperTags()
+
+  def GetDeveloperTags(self):
+    """Retrieve developer tags for this video entry."""
+    developer_tags = []
+    for category in self.media.category:
+      if category.scheme == YOUTUBE_DEVELOPER_TAG_SCHEME:
+        developer_tags.append(category)
+    if len(developer_tags) > 0:
+      return developer_tags
+
+  def GetYouTubeCategoryAsString(self):
+    """Convenience method to return the YouTube category as string.
+
+    YouTubeVideoEntries can contain multiple Category objects with differing 
+        schemes. This method returns only the category with the correct
+        scheme, ignoring developer tags.
+    """
+    for category in self.media.category:
+      if category.scheme != YOUTUBE_DEVELOPER_TAG_SCHEME:
+        return category.text
 
 class YouTubeUserEntry(gdata.GDataEntry):
+  """Represents a user on YouTube."""
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
@@ -458,6 +521,7 @@
 
 
 class YouTubeVideoFeed(gdata.GDataFeed, gdata.LinkFinder):
+  """Represents a video feed on YouTube."""
   _tag = gdata.GDataFeed._tag
   _namespace = gdata.GDataFeed._namespace
   _children = gdata.GDataFeed._children.copy()
@@ -465,6 +529,7 @@
   _children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [YouTubeVideoEntry])
 
 class YouTubePlaylistEntry(gdata.GDataEntry):
+  """Represents a playlist in YouTube."""
   _tag = gdata.GDataEntry._tag
   _namespace = gdata.GDataEntry._namespace
   _children = gdata.GDataEntry._children.copy()
@@ -496,7 +561,7 @@
 
 
 class YouTubePlaylistFeed(gdata.GDataFeed, gdata.LinkFinder):
-  """ A feed of a user's playlists """
+  """Represents a feed of a user's playlists """
   _tag = gdata.GDataFeed._tag
   _namespace = gdata.GDataFeed._namespace
   _children = gdata.GDataFeed._children.copy()
@@ -506,7 +571,7 @@
 
 
 class YouTubePlaylistVideoFeed(gdata.GDataFeed, gdata.LinkFinder):
-  """ A feed of videos in a user's playlist """
+  """Represents a feed of video entry on a playlist."""
   _tag = gdata.GDataFeed._tag
   _namespace = gdata.GDataFeed._namespace
   _children = gdata.GDataFeed._children.copy()
@@ -516,6 +581,7 @@
 
 
 class YouTubeContactFeed(gdata.GDataFeed, gdata.LinkFinder):
+  """Represents a feed of a users contacts."""
   _tag = gdata.GDataFeed._tag
   _namespace = gdata.GDataFeed._namespace
   _children = gdata.GDataFeed._children.copy()
@@ -525,6 +591,7 @@
 
 
 class YouTubeSubscriptionFeed(gdata.GDataFeed, gdata.LinkFinder):
+  """Represents a feed of a users subscriptions."""
   _tag = gdata.GDataFeed._tag
   _namespace = gdata.GDataFeed._namespace
   _children = gdata.GDataFeed._children.copy()
@@ -534,6 +601,7 @@
 
 
 class YouTubeVideoCommentFeed(gdata.GDataFeed, gdata.LinkFinder):
+  """Represents a feed of comments for a video."""
   _tag = gdata.GDataFeed._tag
   _namespace = gdata.GDataFeed._namespace
   _children = gdata.GDataFeed._children.copy()
@@ -543,6 +611,7 @@
 
 
 class YouTubeVideoResponseFeed(gdata.GDataFeed, gdata.LinkFinder):
+  """Represents a feed of video responses."""
   _tag = gdata.GDataFeed._tag
   _namespace = gdata.GDataFeed._namespace
   _children = gdata.GDataFeed._children.copy()

Modified: trunk/conduit/modules/GoogleModule/gdata/youtube/service.py
==============================================================================
--- trunk/conduit/modules/GoogleModule/gdata/youtube/service.py	(original)
+++ trunk/conduit/modules/GoogleModule/gdata/youtube/service.py	Tue Mar 17 09:00:35 2009
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 #
-# Copyright (C) 2006 Google Inc.
+# Copyright (C) 2008 Google Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -16,281 +16,594 @@
 
 """YouTubeService extends GDataService to streamline YouTube operations.
 
-  YouTubeService: Provides methods to perform CRUD operations on YouTube feeds. 
+  YouTubeService: Provides methods to perform CRUD operations on YouTube feeds.
   Extends GDataService.
-
 """
 
 __author__ = ('api stephaniel gmail com (Stephanie Liu), '
               'api jhartmann gmail com (Jochen Hartmann)')
 
 try:
-  from xml.etree import ElementTree
+  from xml.etree import cElementTree as ElementTree
 except ImportError:
-  from elementtree import ElementTree
-import urllib
+  try:
+    import cElementTree as ElementTree
+  except ImportError:
+    try:
+      from xml.etree import ElementTree
+    except ImportError:
+      from elementtree import ElementTree
 import os
-import gdata
 import atom
+import gdata
 import gdata.service
 import gdata.youtube
-# TODO (jhartmann) - rewrite query class structure + allow passing in projections
 
 YOUTUBE_SERVER = 'gdata.youtube.com'
 YOUTUBE_SERVICE = 'youtube'
-YOUTUBE_SUPPORTED_UPLOAD_TYPES = ('mov', 'avi', 'wmv', 'mpg', 'quicktime')
+YOUTUBE_CLIENTLOGIN_AUTHENTICATION_URL = 'https://www.google.com/youtube/accounts/ClientLogin'
+YOUTUBE_SUPPORTED_UPLOAD_TYPES = ('mov', 'avi', 'wmv', 'mpg', 'quicktime',
+                                  'flv')
 YOUTUBE_QUERY_VALID_TIME_PARAMETERS = ('today', 'this_week', 'this_month',
-                                        'all_time')
-YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS = ('updated', 'viewCount', 'rating',
-                                           'relevance')
+                                       'all_time')
+YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS = ('published', 'viewCount', 'rating',
+                                          'relevance')
 YOUTUBE_QUERY_VALID_RACY_PARAMETERS = ('include', 'exclude')
 YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS = ('1', '5', '6')
 YOUTUBE_STANDARDFEEDS = ('most_recent', 'recently_featured',
-                          'top_rated', 'most_viewed','watch_on_mobile')
-
+                         'top_rated', 'most_viewed','watch_on_mobile')
+YOUTUBE_UPLOAD_URI = 'http://uploads.gdata.youtube.com/feeds/api/users'
 YOUTUBE_UPLOAD_TOKEN_URI = 'http://gdata.youtube.com/action/GetUploadToken'
 YOUTUBE_VIDEO_URI = 'http://gdata.youtube.com/feeds/api/videos'
-YOUTUBE_USER_FEED_URI = 'http://gdata.youtube.com/feeds/api/users/'
+YOUTUBE_USER_FEED_URI = 'http://gdata.youtube.com/feeds/api/users'
+YOUTUBE_PLAYLIST_FEED_URI = 'http://gdata.youtube.com/feeds/api/playlists'
 
 YOUTUBE_STANDARD_FEEDS = 'http://gdata.youtube.com/feeds/api/standardfeeds'
-YOUTUBE_STANDARD_TOP_RATED_URI = YOUTUBE_STANDARD_FEEDS + '/top_rated'
-YOUTUBE_STANDARD_MOST_VIEWED_URI = YOUTUBE_STANDARD_FEEDS + '/most_viewed'
-YOUTUBE_STANDARD_RECENTLY_FEATURED_URI = YOUTUBE_STANDARD_FEEDS + (
-    '/recently_featured')
-YOUTUBE_STANDARD_WATCH_ON_MOBILE_URI = YOUTUBE_STANDARD_FEEDS + (
-    '/watch_on_mobile')
-YOUTUBE_STANDARD_TOP_FAVORITES_URI = YOUTUBE_STANDARD_FEEDS + '/top_favorites'
-YOUTUBE_STANDARD_MOST_RECENT_URI = YOUTUBE_STANDARD_FEEDS + '/most_recent'
-YOUTUBE_STANDARD_MOST_DISCUSSED_URI = YOUTUBE_STANDARD_FEEDS + '/most_discussed'
-YOUTUBE_STANDARD_MOST_LINKED_URI = YOUTUBE_STANDARD_FEEDS + '/most_linked'
-YOUTUBE_STANDARD_MOST_RESPONDED_URI = YOUTUBE_STANDARD_FEEDS + '/most_responded'
-
-YOUTUBE_RATING_LINK_REL = 'http://gdata.youtube.com/schemas/2007#video.ratings'
-YOUTUBE_COMPLAINT_CATEGORY_SCHEME = 'http://gdata.youtube.com/schemas/2007/complaint-reasons.cat'
-YOUTUBE_COMPLAINT_CATEGORY_TERMS = ('PORN', 'VIOLENCE', 'HATE', 'DANGEROUS', 
-                                   'RIGHTS', 'SPAM')
+YOUTUBE_STANDARD_TOP_RATED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS, 'top_rated')
+YOUTUBE_STANDARD_MOST_VIEWED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'most_viewed')
+YOUTUBE_STANDARD_RECENTLY_FEATURED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'recently_featured')
+YOUTUBE_STANDARD_WATCH_ON_MOBILE_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'watch_on_mobile')
+YOUTUBE_STANDARD_TOP_FAVORITES_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'top_favorites')
+YOUTUBE_STANDARD_MOST_RECENT_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'most_recent')
+YOUTUBE_STANDARD_MOST_DISCUSSED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'most_discussed')
+YOUTUBE_STANDARD_MOST_LINKED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'most_linked')
+YOUTUBE_STANDARD_MOST_RESPONDED_URI = '%s/%s' % (YOUTUBE_STANDARD_FEEDS,
+    'most_responded')
+YOUTUBE_SCHEMA = 'http://gdata.youtube.com/schemas'
+
+YOUTUBE_RATING_LINK_REL = '%s#video.ratings' % YOUTUBE_SCHEMA
+
+YOUTUBE_COMPLAINT_CATEGORY_SCHEME = '%s/%s' % (YOUTUBE_SCHEMA,
+                                               'complaint-reasons.cat')
+YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME = '%s/%s' % (YOUTUBE_SCHEMA,
+                                                  'subscriptiontypes.cat')
+
+YOUTUBE_COMPLAINT_CATEGORY_TERMS = ('PORN', 'VIOLENCE', 'HATE', 'DANGEROUS',
+                                    'RIGHTS', 'SPAM')
 YOUTUBE_CONTACT_STATUS = ('accepted', 'rejected')
 YOUTUBE_CONTACT_CATEGORY = ('Friends', 'Family')
 
-UNKOWN_ERROR=1000
-YOUTUBE_BAD_REQUEST=400
-YOUTUBE_CONFLICT=409
-YOUTUBE_INTERNAL_SERVER_ERROR=500
-YOUTUBE_INVALID_ARGUMENT=601
-YOUTUBE_INVALID_CONTENT_TYPE=602
-YOUTUBE_NOT_A_VIDEO=603
-YOUTUBE_INVALID_KIND=604
+UNKOWN_ERROR = 1000
+YOUTUBE_BAD_REQUEST = 400
+YOUTUBE_CONFLICT = 409
+YOUTUBE_INTERNAL_SERVER_ERROR = 500
+YOUTUBE_INVALID_ARGUMENT = 601
+YOUTUBE_INVALID_CONTENT_TYPE = 602
+YOUTUBE_NOT_A_VIDEO = 603
+YOUTUBE_INVALID_KIND = 604
+
 
 class Error(Exception):
+  """Base class for errors within the YouTube service."""
   pass
 
 class RequestError(Error):
+  """Error class that is thrown in response to an invalid HTTP Request."""
   pass
 
 class YouTubeError(Error):
+  """YouTube service specific error class."""
   pass
 
 class YouTubeService(gdata.service.GDataService):
-  """Client for the YouTube service."""
+
+  """Client for the YouTube service.
+
+  Performs all documented Google Data YouTube API functions, such as inserting,
+  updating and deleting videos, comments, playlist, subscriptions etc.
+  YouTube Service requires authentication for any write, update or delete
+  actions.
+
+  Attributes:
+    email: An optional string identifying the user. Required only for
+        authenticated actions.
+    password: An optional string identifying the user's password.
+    source: An optional string identifying the name of your application.
+    server: An optional address of the YouTube API server. gdata.youtube.com 
+        is provided as the default value.
+    additional_headers: An optional dictionary containing additional headers
+        to be passed along with each request. Use to store developer key.
+    client_id: An optional string identifying your application, required for   
+        authenticated requests, along with a developer key.
+    developer_key: An optional string value. Register your application at
+        http://code.google.com/apis/youtube/dashboard to obtain a (free) key.
+  """
 
   def __init__(self, email=None, password=None, source=None,
                server=YOUTUBE_SERVER, additional_headers=None, client_id=None,
-               developer_key=None):
-    if client_id and developer_key:
-      self.client_id = client_id
-      self.developer_key = developer_key
-      self.additional_headers = {'X-Gdata-Client': self.client_id,
-                                 'X-GData-Key': 'key=' + self.developer_key}
-      gdata.service.GDataService.__init__(
-          self, email=email, password=password,
-          service=YOUTUBE_SERVICE, source=source, server=server,
-          additional_headers=self.additional_headers)
+               developer_key=None, **kwargs):
+    """Creates a client for the YouTube service.
+
+    Args:
+      email: string (optional) The user's email address, used for
+          authentication.
+      password: string (optional) The user's password.
+      source: string (optional) The name of the user's application.
+      server: string (optional) The name of the server to which a connection
+          will be opened. Default value: 'gdata.youtube.com'.
+      client_id: string (optional) Identifies your application, required for
+          authenticated requests, along with a developer key.
+      developer_key: string (optional) Register your application at
+          http://code.google.com/apis/youtube/dashboard to obtain a (free) key.
+      **kwargs: The other parameters to pass to gdata.service.GDataService
+          constructor.
+    """
+    self.additional_headers = {}
+    if client_id is not None and developer_key is not None:
+      self.additional_headers = {'X-Gdata-Client': client_id,
+                                 'X-GData-Key': 'key=%s' % developer_key}
     elif developer_key and not client_id:
       raise YouTubeError('You must also specify the clientId')
-    else:
-      gdata.service.GDataService.__init__(
-          self, email=email, password=password,
-          service=YOUTUBE_SERVICE, source=source, server=server,
-          additional_headers=additional_headers)
+
+    gdata.service.GDataService.__init__(
+        self, email=email, password=password, service=YOUTUBE_SERVICE,
+        source=source, server=server, additional_headers=additional_headers,
+        **kwargs)
+    self.auth_service_url = YOUTUBE_CLIENTLOGIN_AUTHENTICATION_URL
 
   def GetYouTubeVideoFeed(self, uri):
+    """Retrieve a YouTubeVideoFeed.
+
+    Args:
+      uri: A string representing the URI of the feed that is to be retrieved.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.Get(uri, converter=gdata.youtube.YouTubeVideoFeedFromString)
 
   def GetYouTubeVideoEntry(self, uri=None, video_id=None):
-    if not uri and not video_id:
+    """Retrieve a YouTubeVideoEntry.
+
+    Either a uri or a video_id must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the entry that is to 
+          be retrieved.
+      video_id: An optional string representing the ID of the video.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a video_id to the
+          GetYouTubeVideoEntry() method.
+    """
+    if uri is None and video_id is None:
       raise YouTubeError('You must provide at least a uri or a video_id '
                          'to the GetYouTubeVideoEntry() method')
     elif video_id and not uri:
-      uri = YOUTUBE_VIDEO_URI + '/' + video_id
-
+      uri = '%s/%s' % (YOUTUBE_VIDEO_URI, video_id)
     return self.Get(uri, converter=gdata.youtube.YouTubeVideoEntryFromString)
 
-  def GetYouTubeContactFeed(self, uri=None, username=None):
-    if not uri and not username:
-      raise YouTubeError('You must provide at least a uri or a username '
-                         'to the GetYouTubeContactFeed() method')
-    elif username and not uri:
-      uri = YOUTUBE_USER_FEED_URI + username + '/contacts'
+  def GetYouTubeContactFeed(self, uri=None, username='default'):
+    """Retrieve a YouTubeContactFeed.
 
+    Either a uri or a username must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the contact feed that
+          is to be retrieved.
+      username: An optional string representing the username. Defaults to the
+          currently authenticated user.
+
+    Returns:
+      A YouTubeContactFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a username to the
+          GetYouTubeContactFeed() method.
+    """
+    if uri is None:
+      uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'contacts')
     return self.Get(uri, converter=gdata.youtube.YouTubeContactFeedFromString)
 
-  def GetYouTubeContactEntry(self, uri=None):
+  def GetYouTubeContactEntry(self, uri):
+    """Retrieve a YouTubeContactEntry.
+
+    Args:
+      uri: A string representing the URI of the contact entry that is to
+          be retrieved.
+
+    Returns:
+      A YouTubeContactEntry if successfully retrieved.
+    """
     return self.Get(uri, converter=gdata.youtube.YouTubeContactEntryFromString)
 
   def GetYouTubeVideoCommentFeed(self, uri=None, video_id=None):
-    if not uri and not video_id:
+    """Retrieve a YouTubeVideoCommentFeed.
+
+    Either a uri or a video_id must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the comment feed that
+          is to be retrieved.
+      video_id: An optional string representing the ID of the video for which
+          to retrieve the comment feed.
+
+    Returns:
+      A YouTubeVideoCommentFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a video_id to the
+          GetYouTubeVideoCommentFeed() method.
+    """
+    if uri is None and video_id is None:
       raise YouTubeError('You must provide at least a uri or a video_id '
                          'to the GetYouTubeVideoCommentFeed() method')
     elif video_id and not uri:
-      uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/comments'
-
+      uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'comments')
     return self.Get(
-        uri,
-        converter=gdata.youtube.YouTubeVideoCommentFeedFromString)
+        uri, converter=gdata.youtube.YouTubeVideoCommentFeedFromString)
 
   def GetYouTubeVideoCommentEntry(self, uri):
+    """Retrieve a YouTubeVideoCommentEntry.
+
+    Args:
+      uri: A string representing the URI of the comment entry that is to
+          be retrieved.
+
+    Returns:
+      A YouTubeCommentEntry if successfully retrieved.
+    """
     return self.Get(
-        uri,
-        converter=gdata.youtube.YouTubeVideoCommentEntryFromString)
+        uri, converter=gdata.youtube.YouTubeVideoCommentEntryFromString)
 
   def GetYouTubeUserFeed(self, uri=None, username=None):
-    if not uri and not username:
+    """Retrieve a YouTubeUserFeed.
+
+    Either a uri or a username must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the user feed that is
+          to be retrieved.
+      username: An optional string representing the username.
+
+    Returns:
+      A YouTubeUserFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a username to the
+          GetYouTubeUserFeed() method.
+    """
+    if uri is None and username is None:
       raise YouTubeError('You must provide at least a uri or a username '
                          'to the GetYouTubeUserFeed() method')
     elif username and not uri:
-      uri = YOUTUBE_USER_FEED_URI + username + '/uploads'
-
+      uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'uploads')
     return self.Get(uri, converter=gdata.youtube.YouTubeUserFeedFromString)
 
   def GetYouTubeUserEntry(self, uri=None, username=None):
-    if not uri and not username:
+    """Retrieve a YouTubeUserEntry.
+
+    Either a uri or a username must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the user entry that is
+          to be retrieved.
+      username: An optional string representing the username.
+
+    Returns:
+      A YouTubeUserEntry if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a username to the
+          GetYouTubeUserEntry() method.
+    """
+    if uri is None and username is None:
       raise YouTubeError('You must provide at least a uri or a username '
                          'to the GetYouTubeUserEntry() method')
     elif username and not uri:
-      uri = YOUTUBE_USER_FEED_URI + username
-
+      uri = '%s/%s' % (YOUTUBE_USER_FEED_URI, username)
     return self.Get(uri, converter=gdata.youtube.YouTubeUserEntryFromString)
 
-  def GetYouTubePlaylistFeed(self, uri=None, username=None):
-    if not uri and not username:
-      raise YouTubeError('You must provide at least a uri or a username '
-                         'to the GetYouTubePlaylistFeed() method')
-    elif username and not uri:
-      uri = YOUTUBE_USER_FEED_URI + username + '/playlists'
+  def GetYouTubePlaylistFeed(self, uri=None, username='default'):
+    """Retrieve a YouTubePlaylistFeed (a feed of playlists for a user).
+
+    Either a uri or a username must be provided.
 
+    Args:
+      uri: An optional string representing the URI of the playlist feed that
+          is to be retrieved.
+      username: An optional string representing the username. Defaults to the
+          currently authenticated user.
+
+    Returns:
+      A YouTubePlaylistFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a username to the
+          GetYouTubePlaylistFeed() method.
+    """
+    if uri is None:
+      uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'playlists')
     return self.Get(uri, converter=gdata.youtube.YouTubePlaylistFeedFromString)
 
   def GetYouTubePlaylistEntry(self, uri):
+    """Retrieve a YouTubePlaylistEntry.
+
+    Args:
+      uri: A string representing the URI of the playlist feed that is to
+          be retrieved.
+
+    Returns:
+      A YouTubePlaylistEntry if successfully retrieved.
+    """
     return self.Get(uri, converter=gdata.youtube.YouTubePlaylistEntryFromString)
 
   def GetYouTubePlaylistVideoFeed(self, uri=None, playlist_id=None):
-    if not uri and not playlist_id:
+    """Retrieve a YouTubePlaylistVideoFeed (a feed of videos on a playlist).
+
+    Either a uri or a playlist_id must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the playlist video feed
+          that is to be retrieved.
+      playlist_id: An optional string representing the Id of the playlist whose
+          playlist video feed is to be retrieved.
+
+    Returns:
+      A YouTubePlaylistVideoFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a playlist_id to the
+          GetYouTubePlaylistVideoFeed() method.
+    """
+    if uri is None and playlist_id is None:
       raise YouTubeError('You must provide at least a uri or a playlist_id '
                          'to the GetYouTubePlaylistVideoFeed() method')
     elif playlist_id and not uri:
-      uri = 'http://gdata.youtube.com/feeds/api/playlists/' + playlist_id
-
+      uri = '%s/%s' % (YOUTUBE_PLAYLIST_FEED_URI, playlist_id)
     return self.Get(
-        uri,
-        converter=gdata.youtube.YouTubePlaylistVideoFeedFromString)
+        uri, converter=gdata.youtube.YouTubePlaylistVideoFeedFromString)
 
   def GetYouTubeVideoResponseFeed(self, uri=None, video_id=None):
-    if not uri and not video_id:
+    """Retrieve a YouTubeVideoResponseFeed.
+
+    Either a uri or a playlist_id must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the video response feed
+          that is to be retrieved.
+      video_id: An optional string representing the ID of the video whose
+          response feed is to be retrieved.
+
+    Returns:
+      A YouTubeVideoResponseFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a video_id to the
+          GetYouTubeVideoResponseFeed() method.
+    """
+    if uri is None and video_id is None:
       raise YouTubeError('You must provide at least a uri or a video_id '
                          'to the GetYouTubeVideoResponseFeed() method')
     elif video_id and not uri:
-      uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/responses'
-
-    return self.Get(uri,
-                    converter=gdata.youtube.YouTubeVideoResponseFeedFromString)
+      uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'responses')
+    return self.Get(
+        uri, converter=gdata.youtube.YouTubeVideoResponseFeedFromString)
 
   def GetYouTubeVideoResponseEntry(self, uri):
-    return self.Get(uri,
-                    converter=gdata.youtube.YouTubeVideoResponseEntryFromString)
+    """Retrieve a YouTubeVideoResponseEntry.
 
-  def GetYouTubeSubscriptionFeed(self, uri=None, username=None):
-    if not uri and not username:
-      raise YouTubeError('You must provide at least a uri or a username '
-                         'to the GetYouTubeSubscriptionFeed() method')
-    elif username and not uri:
-      uri = ('http://gdata.youtube.com'
-             '/feeds/users/') + username + '/subscriptions'
+    Args:
+      uri: A string representing the URI of the video response entry that
+          is to be retrieved.
 
+    Returns:
+      A YouTubeVideoResponseEntry if successfully retrieved.
+    """
     return self.Get(
-        uri,
-        converter=gdata.youtube.YouTubeSubscriptionFeedFromString)
+        uri, converter=gdata.youtube.YouTubeVideoResponseEntryFromString)
+
+  def GetYouTubeSubscriptionFeed(self, uri=None, username='default'):
+    """Retrieve a YouTubeSubscriptionFeed.
+
+    Either the uri of the feed or a username must be provided.
+
+    Args:
+      uri: An optional string representing the URI of the feed that is to
+          be retrieved.
+      username: An optional string representing the username whose subscription
+          feed is to be retrieved. Defaults to the currently authenticted user.
+
+    Returns:
+      A YouTubeVideoSubscriptionFeed if successfully retrieved.
+    """
+    if uri is None:
+      uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'subscriptions')
+    return self.Get(
+        uri, converter=gdata.youtube.YouTubeSubscriptionFeedFromString)
 
   def GetYouTubeSubscriptionEntry(self, uri):
-    return self.Get(uri,
-                    converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
+    """Retrieve a YouTubeSubscriptionEntry.
+
+    Args:
+      uri: A string representing the URI of the entry that is to be retrieved.
+
+    Returns:
+      A YouTubeVideoSubscriptionEntry if successfully retrieved.
+    """
+    return self.Get(
+        uri, converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
 
   def GetYouTubeRelatedVideoFeed(self, uri=None, video_id=None):
-    if not uri and not video_id:
+    """Retrieve a YouTubeRelatedVideoFeed.
+
+    Either a uri for the feed or a video_id is required.
+
+    Args:
+      uri: An optional string representing the URI of the feed that is to
+          be retrieved.
+      video_id: An optional string representing the ID of the video for which
+          to retrieve the related video feed.
+
+    Returns:
+      A YouTubeRelatedVideoFeed if successfully retrieved.
+
+    Raises:
+      YouTubeError: You must provide at least a uri or a video_id to the
+          GetYouTubeRelatedVideoFeed() method.
+    """
+    if uri is None and video_id is None:
       raise YouTubeError('You must provide at least a uri or a video_id '
                          'to the GetYouTubeRelatedVideoFeed() method')
     elif video_id and not uri:
-      uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/related'
-
-    return self.Get(uri,
-                    converter=gdata.youtube.YouTubeVideoFeedFromString)
+      uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'related')
+    return self.Get(
+        uri, converter=gdata.youtube.YouTubeVideoFeedFromString)
 
   def GetTopRatedVideoFeed(self):
+    """Retrieve the 'top_rated' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_TOP_RATED_URI)
 
   def GetMostViewedVideoFeed(self):
+    """Retrieve the 'most_viewed' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_VIEWED_URI)
 
   def GetRecentlyFeaturedVideoFeed(self):
+    """Retrieve the 'recently_featured' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_RECENTLY_FEATURED_URI)
 
   def GetWatchOnMobileVideoFeed(self):
+    """Retrieve the 'watch_on_mobile' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_WATCH_ON_MOBILE_URI)
 
   def GetTopFavoritesVideoFeed(self):
+    """Retrieve the 'top_favorites' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_TOP_FAVORITES_URI)
 
   def GetMostRecentVideoFeed(self):
+    """Retrieve the 'most_recent' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_RECENT_URI)
 
   def GetMostDiscussedVideoFeed(self):
+    """Retrieve the 'most_discussed' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_DISCUSSED_URI)
 
   def GetMostLinkedVideoFeed(self):
+    """Retrieve the 'most_linked' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_LINKED_URI)
 
   def GetMostRespondedVideoFeed(self):
+    """Retrieve the 'most_responded' standard video feed.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
     return self.GetYouTubeVideoFeed(YOUTUBE_STANDARD_MOST_RESPONDED_URI)
 
   def GetUserFavoritesFeed(self, username='default'):
-    return self.GetYouTubeVideoFeed('http://gdata.youtube.com/feeds/api/users/'
-                                    + username + '/favorites')
+    """Retrieve the favorites feed for a given user.
+
+    Args:
+      username: An optional string representing the username whose favorites
+          feed is to be retrieved. Defaults to the currently authenticated user.
+
+    Returns:
+      A YouTubeVideoFeed if successfully retrieved.
+    """
+    favorites_feed_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username,
+                                       'favorites')
+    return self.GetYouTubeVideoFeed(favorites_feed_uri)
 
   def InsertVideoEntry(self, video_entry, filename_or_handle,
                        youtube_username='default',
                        content_type='video/quicktime'):
-    """Upload a new video to YouTube using the direct upload mechanism
+    """Upload a new video to YouTube using the direct upload mechanism.
 
     Needs authentication.
 
-    Arguments:
-      video_entry: The YouTubeVideoEntry to upload
+    Args:
+      video_entry: The YouTubeVideoEntry to upload.
       filename_or_handle: A file-like object or file name where the video
-          will be read from
-      youtube_username: (optional) Username into whose account this video is
-          to be uploaded to. Defaults to the currently authenticated user.
-      content_type (optional): Internet media type (a.k.a. mime type) of
-          media object. Currently the YouTube API supports these types:
+          will be read from.
+      youtube_username: An optional string representing the username into whose
+          account this video is to be uploaded to. Defaults to the currently
+          authenticated user.
+      content_type: An optional string representing internet media type
+          (a.k.a. mime type) of the media object. Currently the YouTube API
+          supports these types:
             o video/mpeg
             o video/quicktime
             o video/x-msvideo
             o video/mp4
+            o video/x-flv
 
     Returns:
-      The newly created YouTubeVideoEntry or a YouTubeError
+      The newly created YouTubeVideoEntry if successful.
 
+    Raises:
+      AssertionError: video_entry must be a gdata.youtube.VideoEntry instance.
+      YouTubeError: An error occurred trying to read the video file provided.
+      gdata.service.RequestError: An error occurred trying to upload the video
+          to the API server.
     """
 
-    # check to make sure we have a valid video entry
+    # We need to perform a series of checks on the video_entry and on the
+    # file that we plan to upload, such as checking whether we have a valid
+    # video_entry and that the file is the correct type and readable, prior
+    # to performing the actual POST request.
+
     try:
       assert(isinstance(video_entry, gdata.youtube.YouTubeVideoEntry))
     except AssertionError:
@@ -298,23 +611,22 @@
           'body':'`video_entry` must be a gdata.youtube.VideoEntry instance',
           'reason':'Found %s, not VideoEntry' % type(video_entry)
           })
+    majtype, mintype = content_type.split('/')
 
-    # check to make sure the MIME type is supported
     try:
-      majtype, mintype = content_type.split('/')
       assert(mintype in YOUTUBE_SUPPORTED_UPLOAD_TYPES)
     except (ValueError, AssertionError):
       raise YouTubeError({'status':YOUTUBE_INVALID_CONTENT_TYPE,
           'body':'This is not a valid content type: %s' % content_type,
           'reason':'Accepted content types: %s' %
-              ['video/' + t for t in YOUTUBE_SUPPORTED_UPLOAD_TYPES]
-          })
-    # check that the video file is valid and readable
+              ['video/%s' % (t) for t in YOUTUBE_SUPPORTED_UPLOAD_TYPES]})
+
     if (isinstance(filename_or_handle, (str, unicode)) 
         and os.path.exists(filename_or_handle)):
       mediasource = gdata.MediaSource()
       mediasource.setFile(filename_or_handle, content_type)
     elif hasattr(filename_or_handle, 'read'):
+      import StringIO
       if hasattr(filename_or_handle, 'seek'):
         filename_or_handle.seek(0)
       file_handle = StringIO.StringIO(filename_or_handle.read())
@@ -328,13 +640,11 @@
           '`filename_or_handle` must be a path name or a file-like object',
           'reason': ('Found %s, not path name or object '
                      'with a .read() method' % type(filename_or_handle))})
-
-    upload_uri = ('http://uploads.gdata.youtube.com/feeds/api/users/' + 
-                  youtube_username + '/uploads')
-
+    upload_uri = '%s/%s/%s' % (YOUTUBE_UPLOAD_URI, youtube_username,
+                              'uploads')
     self.additional_headers['Slug'] = mediasource.file_name
 
-    # post the video file
+    # Using a nested try statement to retain Python 2.4 compatibility
     try:
       try:
         return self.Post(video_entry, uri=upload_uri, media_source=mediasource,
@@ -345,20 +655,24 @@
       del(self.additional_headers['Slug'])
 
   def CheckUploadStatus(self, video_entry=None, video_id=None):
-    """Check upload status on a recently uploaded video entry
+    """Check upload status on a recently uploaded video entry.
 
-    Needs authentication.
+    Needs authentication. Either video_entry or video_id must be provided.
 
-    Arguments:
-      video_entry: (optional) The YouTubeVideoEntry to upload
-      video_id: (optional) The videoId of a recently uploaded entry. One of
-          these two arguments will need to be present.
+    Args:
+      video_entry: An optional YouTubeVideoEntry whose upload status to check
+      video_id: An optional string representing the ID of the uploaded video
+          whose status is to be checked.
 
     Returns:
       A tuple containing (video_upload_state, detailed_message) or None if
           no status information is found.
+
+    Raises:
+      YouTubeError: You must provide at least a video_entry or a video_id to the
+          CheckUploadStatus() method.
     """
-    if not video_entry and not video_id:
+    if video_entry is None and video_id is None:
       raise YouTubeError('You must provide at least a uri or a video_id '
                          'to the CheckUploadStatus() method')
     elif video_id and not video_entry:
@@ -379,19 +693,25 @@
             return (state_value, message)
 
   def GetFormUploadToken(self, video_entry, uri=YOUTUBE_UPLOAD_TOKEN_URI):
-    """Receives a YouTube Token and a YouTube PostUrl with which to construct
-    the HTML Upload form for browser-based video uploads
+    """Receives a YouTube Token and a YouTube PostUrl from a YouTubeVideoEntry.
 
     Needs authentication.
 
-    Arguments:
-        video_entry: The YouTubeVideoEntry to upload (meta-data only)
-        uri: (optional) A url from where to fetch the token information
+    Args:
+      video_entry: The YouTubeVideoEntry to upload (meta-data only).
+      uri: An optional string representing the URI from where to fetch the
+          token information. Defaults to the YOUTUBE_UPLOADTOKEN_URI.
 
     Returns:
-        A tuple containing (post_url, youtube_token)
+      A tuple containing the URL to which to post your video file, along
+          with the youtube token that must be included with your upload in the
+          form of: (post_url, youtube_token).
     """
-    response = self.Post(video_entry, uri)
+    try:
+      response = self.Post(video_entry, uri)
+    except gdata.service.RequestError, e:
+      raise YouTubeError(e.args[0])
+
     tree = ElementTree.fromstring(response)
 
     for child in tree:
@@ -399,60 +719,59 @@
         post_url = child.text
       elif child.tag == 'token':
         youtube_token = child.text
-
     return (post_url, youtube_token)
 
   def UpdateVideoEntry(self, video_entry):
-    """Updates a video entry's meta-data
+    """Updates a video entry's meta-data.
 
     Needs authentication.
 
-    Arguments:
-        video_entry: The YouTubeVideoEntry to update, containing updated 
-            meta-data
+    Args:
+      video_entry: The YouTubeVideoEntry to update, containing updated
+          meta-data.
 
     Returns:
-        An updated YouTubeVideoEntry on success or None
+      An updated YouTubeVideoEntry on success or None.
     """
     for link in video_entry.link:
       if link.rel == 'edit':
         edit_uri = link.href
-
     return self.Put(video_entry, uri=edit_uri,
                     converter=gdata.youtube.YouTubeVideoEntryFromString)
 
   def DeleteVideoEntry(self, video_entry):
-    """Deletes a video entry
+    """Deletes a video entry.
 
     Needs authentication.
 
-    Arguments:
-        video_entry: The YouTubeVideoEntry to be deleted
+    Args:
+      video_entry: The YouTubeVideoEntry to be deleted.
 
     Returns:
-        True if entry was deleted successfully
+      True if entry was deleted successfully.
     """
     for link in video_entry.link:
       if link.rel == 'edit':
         edit_uri = link.href
-
     return self.Delete(edit_uri)
 
   def AddRating(self, rating_value, video_entry):
-    """Add a rating to a video entry
+    """Add a rating to a video entry.
 
     Needs authentication.
 
-    Arguments:
-        rating_value: The value for the rating (between 1 and 5)
-        video_entry: The YouTubeVideoEntry to be rated
+    Args:
+      rating_value: The integer value for the rating (between 1 and 5).
+      video_entry: The YouTubeVideoEntry to be rated.
 
     Returns:
-      True if the rating was added successfully
-    """
+      True if the rating was added successfully.
 
+    Raises:
+      YouTubeError: rating_value must be between 1 and 5 in AddRating().
+    """
     if rating_value < 1 or rating_value > 5:
-      raise YouTubeError('AddRating: rating_value must be between 1 and 5')
+      raise YouTubeError('rating_value must be between 1 and 5 in AddRating()')
 
     entry = gdata.GDataEntry()
     rating = gdata.youtube.Rating(min='1', max='5')
@@ -467,16 +786,17 @@
     return self.Post(entry, uri=rating_uri)
 
   def AddComment(self, comment_text, video_entry):
-    """Add a comment to a video entry
+    """Add a comment to a video entry.
 
-    Needs authentication.
+    Needs authentication. Note that each comment that is posted must contain
+        the video entry that it is to be posted to.
 
-    Arguments:
-        comment_text: The text of the comment
-        video_entry: The YouTubeVideoEntry to be commented on
+    Args:
+      comment_text: A string representing the text of the comment.
+      video_entry: The YouTubeVideoEntry to be commented on.
 
     Returns:
-      True if the comment was added successfully
+      True if the comment was added successfully.
     """
     content = atom.Content(text=comment_text)
     comment_entry = gdata.youtube.YouTubeVideoCommentEntry(content=content)
@@ -485,109 +805,119 @@
     return self.Post(comment_entry, uri=comment_post_uri)
 
   def AddVideoResponse(self, video_id_to_respond_to, video_response):
-    """Add a video response
+    """Add a video response.
 
     Needs authentication.
 
-    Arguments:
-        video_id_to_respond_to: Id of the YouTubeVideoEntry to be responded to
-        video_response: YouTubeVideoEntry to be posted as a response
+    Args:
+      video_id_to_respond_to: A string representing the ID of the video to be
+          responded to.
+      video_response: YouTubeVideoEntry to be posted as a response.
 
     Returns:
-        True if video response was posted successfully
+      True if video response was posted successfully.
     """
-    post_uri = YOUTUBE_VIDEO_URI + '/' + video_id_to_respond_to + '/responses'
+    post_uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id_to_respond_to,
+                             'responses')
     return self.Post(video_response, uri=post_uri)
 
   def DeleteVideoResponse(self, video_id, response_video_id):
-    """Delete a video response
+    """Delete a video response.
 
     Needs authentication.
 
-    Arguments:
-        video_id: Id of YouTubeVideoEntry that contains the response
-        response_video_id: Id of the YouTubeVideoEntry posted as response
+    Args:
+      video_id: A string representing the ID of video that contains the
+          response.
+      response_video_id: A string representing the ID of the video that was
+          posted as a response.
 
     Returns:
-        True if video response was deleted succcessfully
+      True if video response was deleted succcessfully.
     """
-    delete_uri = (YOUTUBE_VIDEO_URI + '/' + video_id + 
-                  '/responses/' + response_video_id)
-
+    delete_uri = '%s/%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'responses',
+                                  response_video_id)
     return self.Delete(delete_uri)
 
   def AddComplaint(self, complaint_text, complaint_term, video_id):
-    """Add a complaint for a particular video entry
+    """Add a complaint for a particular video entry.
 
     Needs authentication.
 
-    Arguments:
-        complaint_text: Text explaining the complaint
-        complaint_term: Complaint category term
-        video_id: Id of YouTubeVideoEntry to complain about
+    Args:
+      complaint_text: A string representing the complaint text.
+      complaint_term: A string representing the complaint category term.
+      video_id: A string representing the ID of YouTubeVideoEntry to
+          complain about.
 
     Returns:
-        True if posted successfully
+      True if posted successfully.
+
+    Raises:
+      YouTubeError: Your complaint_term is not valid.
     """
     if complaint_term not in YOUTUBE_COMPLAINT_CATEGORY_TERMS:
-      raise YouTubeError('Your complaint must be a valid term')
+      raise YouTubeError('Your complaint_term is not valid')
 
     content = atom.Content(text=complaint_text)
     category = atom.Category(term=complaint_term,
                              scheme=YOUTUBE_COMPLAINT_CATEGORY_SCHEME)
 
     complaint_entry = gdata.GDataEntry(content=content, category=[category])
-    post_uri = YOUTUBE_VIDEO_URI + '/' + video_id + '/complaints'
+    post_uri = '%s/%s/%s' % (YOUTUBE_VIDEO_URI, video_id, 'complaints')
 
     return self.Post(complaint_entry, post_uri)
 
   def AddVideoEntryToFavorites(self, video_entry, username='default'):
-    """Add a video entry to a users favorite feed
+    """Add a video entry to a users favorite feed.
 
     Needs authentication.
 
-    Arguments:
-        video_entry: The YouTubeVideoEntry to add
-        username: (optional) The username to whose favorite feed you wish to
-            add the entry. Your client must be authenticated to the username's
-            account.
+    Args:
+      video_entry: The YouTubeVideoEntry to add.
+      username: An optional string representing the username to whose favorite
+          feed you wish to add the entry. Defaults to the currently
+          authenticated user.
     Returns:
-        A GDataEntry if posted successfully
+        The posted YouTubeVideoEntry if successfully posted.
     """
-    post_uri = ('http://gdata.youtube.com/feeds/api/users/' + 
-                username + '/favorites')
+    post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'favorites')
 
-    return self.Post(video_entry, post_uri)
+    return self.Post(video_entry, post_uri,
+                     converter=gdata.youtube.YouTubeVideoEntryFromString)
 
   def DeleteVideoEntryFromFavorites(self, video_id, username='default'):
-    """Delete a video entry from the users favorite feed
+    """Delete a video entry from the users favorite feed.
 
     Needs authentication.
 
-    Arguments:
-        video_id: The Id for the YouTubeVideoEntry to be removed
-        username: (optional) The username of the user's favorite feed. Defaults
-            to the currently authenticated user.
+    Args:
+      video_id: A string representing the ID of the video that is to be removed
+      username: An optional string representing the username of the user's
+          favorite feed. Defaults to the currently authenticated user.
 
     Returns:
-        True if entry was successfully deleted
+        True if entry was successfully deleted.
     """
-    edit_link = YOUTUBE_USER_FEED_URI + username + '/favorites/' + video_id
+    edit_link = '%s/%s/%s/%s' % (YOUTUBE_USER_FEED_URI, username, 'favorites',
+                                 video_id)
     return self.Delete(edit_link)
 
-  def AddPlaylist(self, playlist_title, playlist_description, 
+  def AddPlaylist(self, playlist_title, playlist_description,
                   playlist_private=None):
-    """Add a new playlist to the currently authenticated users account
+    """Add a new playlist to the currently authenticated users account.
 
-    Needs authentication
+    Needs authentication.
+
+    Args:
+      playlist_title: A string representing the title for the new playlist.
+      playlist_description: A string representing the description of the
+          playlist.
+      playlist_private: An optional boolean, set to True if the playlist is
+          to be private.
 
-    Arguments:
-        playlist_title: The title for the new playlist
-        playlist_description: The description for the playlist
-        playlist_private: (optiona) Submit as True if the playlist is to be
-            private
     Returns:
-        A new YouTubePlaylistEntry if successfully posted
+      The YouTubePlaylistEntry if successfully posted.
     """
     playlist_entry = gdata.youtube.YouTubePlaylistEntry(
         title=atom.Title(text=playlist_title),
@@ -595,41 +925,77 @@
     if playlist_private:
       playlist_entry.private = gdata.youtube.Private()
 
-    playlist_post_uri = YOUTUBE_USER_FEED_URI + 'default/playlists'
+    playlist_post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, 'default', 
+                                      'playlists')
     return self.Post(playlist_entry, playlist_post_uri,
                      converter=gdata.youtube.YouTubePlaylistEntryFromString)
 
+  def UpdatePlaylist(self, playlist_id, new_playlist_title,
+                     new_playlist_description, playlist_private=None,
+                     username='default'):
+    """Update a playlist with new meta-data.
+
+    Needs authentication.
+
+    Args:
+      playlist_id: A string representing the ID of the playlist to be updated.
+      new_playlist_title: A string representing a new title for the playlist.
+      new_playlist_description: A string representing a new description for the
+          playlist.
+      playlist_private: An optional boolean, set to True if the playlist is
+          to be private.
+      username: An optional string representing the username whose playlist is
+          to be updated. Defaults to the currently authenticated user.
+
+   Returns:
+      A YouTubePlaylistEntry if the update was successful.
+    """
+    updated_playlist = gdata.youtube.YouTubePlaylistEntry(
+        title=atom.Title(text=new_playlist_title),
+        description=gdata.youtube.Description(text=new_playlist_description))
+    if playlist_private:
+      updated_playlist.private = gdata.youtube.Private()
+
+    playlist_put_uri = '%s/%s/playlists/%s' % (YOUTUBE_USER_FEED_URI, username,
+                                               playlist_id)
+
+    return self.Put(updated_playlist, playlist_put_uri,
+                    converter=gdata.youtube.YouTubePlaylistEntryFromString)
+
   def DeletePlaylist(self, playlist_uri):
-    """Delete a playlist from the currently authenticated users playlists
+    """Delete a playlist from the currently authenticated users playlists.
 
-    Needs authentication
+    Needs authentication.
 
-    Arguments:
-        playlist_uri: The uri of the playlist to delete
+    Args:
+      playlist_uri: A string representing the URI of the playlist that is
+          to be deleted.
 
     Returns:
-        True if successfully deleted
+      True if successfully deleted.
     """
     return self.Delete(playlist_uri)
 
-  def AddPlaylistVideoEntryToPlaylist(self, playlist_uri, video_id, 
-                                      custom_video_title=None,
-                                      custom_video_description=None):
+  def AddPlaylistVideoEntryToPlaylist(
+      self, playlist_uri, video_id, custom_video_title=None,
+      custom_video_description=None):
     """Add a video entry to a playlist, optionally providing a custom title
-    and description
+    and description.
 
-    Needs authentication
+    Needs authentication.
 
-    Arguments:
-        playlist_uri: Uri of playlist to add this video to.
-        video_id: Id of the video entry to add
-        custom_video_title: (optional) Custom title for the video
-        custom_video_description: (optional) Custom video description
+    Args:
+      playlist_uri: A string representing the URI of the playlist to which this
+          video entry is to be added.
+      video_id: A string representing the ID of the video entry to add.
+      custom_video_title: An optional string representing a custom title for
+          the video (only shown on the playlist).
+      custom_video_description: An optional string representing a custom
+          description for the video (only shown on the playlist).
 
     Returns:
-        A YouTubePlaylistVideoEntry if successfully posted
+      A YouTubePlaylistVideoEntry if successfully posted.
     """
-
     playlist_video_entry = gdata.youtube.YouTubePlaylistVideoEntry(
         atom_id=atom.Id(text=video_id))
     if custom_video_title:
@@ -637,26 +1003,30 @@
     if custom_video_description:
       playlist_video_entry.description = gdata.youtube.Description(
           text=custom_video_description)
+
     return self.Post(playlist_video_entry, playlist_uri,
                     converter=gdata.youtube.YouTubePlaylistVideoEntryFromString)
 
-  def UpdatePlaylistVideoEntryMetaData(self, playlist_uri, playlist_entry_id,
-                                       new_video_title,
-                                       new_video_description,
-                                       new_video_position):
-    """Update the meta data for a YouTubePlaylistVideoEntry
+  def UpdatePlaylistVideoEntryMetaData(
+      self, playlist_uri, playlist_entry_id, new_video_title, 
+      new_video_description, new_video_position):
+    """Update the meta data for a YouTubePlaylistVideoEntry.
 
-    Needs authentication
+    Needs authentication.
 
-    Arguments:
-        playlist_uri: Uri of the playlist that contains the entry to be updated
-        playlist_entry_id: Id of the entry to be updated
-        new_video_title: New title for the video entry
-        new_video_description: New description for the video entry
-        new_video_position: New position for the video
+    Args:
+      playlist_uri: A string representing the URI of the playlist that contains
+          the entry to be updated.
+      playlist_entry_id: A string representing the ID of the entry to be
+          updated.
+      new_video_title: A string representing the new title for the video entry.
+      new_video_description: A string representing the new description for
+          the video entry.
+      new_video_position: An integer representing the new position on the
+          playlist for the video.
 
     Returns:
-        A YouTubePlaylistVideoEntry if the update was successful
+      A YouTubePlaylistVideoEntry if the update was successful.
     """
     playlist_video_entry = gdata.youtube.YouTubePlaylistVideoEntry(
         title=atom.Title(text=new_video_title),
@@ -665,49 +1035,75 @@
 
     playlist_put_uri = playlist_uri + '/' + playlist_entry_id
 
-    return self.Put(playlist_video_entry, playlist_put_uri, 
+    return self.Put(playlist_video_entry, playlist_put_uri,
                     converter=gdata.youtube.YouTubePlaylistVideoEntryFromString)
 
+  def DeletePlaylistVideoEntry(self, playlist_uri, playlist_video_entry_id):
+    """Delete a playlist video entry from a playlist.
 
-  def AddSubscriptionToChannel(self, username):
-    """Add a new channel subscription to the currently authenticated users 
-    account
+    Needs authentication.
 
-    Needs authentication
+    Args:
+      playlist_uri: A URI representing the playlist from which the playlist
+          video entry is to be removed from.
+      playlist_video_entry_id: A string representing id of the playlist video
+          entry that is to be removed.
 
-    Arguments:
-        username: The username of the channel to subscribe to.
+    Returns:
+        True if entry was successfully deleted.
+    """
+    delete_uri = '%s/%s' % (playlist_uri, playlist_video_entry_id)
+    return self.Delete(delete_uri)
+
+  def AddSubscriptionToChannel(self, username_to_subscribe_to,
+                               my_username = 'default'):
+    """Add a new channel subscription to the currently authenticated users
+    account.
+
+    Needs authentication.
+
+    Args:
+      username_to_subscribe_to: A string representing the username of the 
+          channel to which we want to subscribe to.
+      my_username: An optional string representing the name of the user which
+          we want to subscribe. Defaults to currently authenticated user.
 
     Returns:
-        A new YouTubeSubscriptionEntry if successfully posted
+      A new YouTubeSubscriptionEntry if successfully posted.
     """
     subscription_category = atom.Category(
-        scheme='http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat',
+        scheme=YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME,
         term='channel')
-    subscription_username = gdata.youtube.Username(text=username)
+    subscription_username = gdata.youtube.Username(
+        text=username_to_subscribe_to)
 
     subscription_entry = gdata.youtube.YouTubeSubscriptionEntry(
         category=subscription_category,
         username=subscription_username)
 
-    post_uri = YOUTUBE_USER_FEED_URI + 'default/subscriptions'
+    post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username, 
+                             'subscriptions')
+
     return self.Post(subscription_entry, post_uri,
                      converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
 
-  def AddSubscriptionToFavorites(self, username):
+  def AddSubscriptionToFavorites(self, username, my_username = 'default'):
     """Add a new subscription to a users favorites to the currently
-    authenticated user's account
+    authenticated user's account.
 
     Needs authentication
 
-    Arguments:
-        username: The username of the users favorite feed to subscribe to
+    Args:
+      username: A string representing the username of the user's favorite feed
+          to subscribe to.
+      my_username: An optional string representing the username of the user
+          that is to be subscribed. Defaults to currently authenticated user.
 
     Returns:
-        A new YouTubeSubscriptionEntry if successful
+        A new YouTubeSubscriptionEntry if successful.
     """
     subscription_category = atom.Category(
-        scheme='http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat',
+        scheme=YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME,
         term='favorites')
     subscription_username = gdata.youtube.Username(text=username)
 
@@ -715,34 +1111,70 @@
         category=subscription_category,
         username=subscription_username)
 
-    post_uri = YOUTUBE_USER_FEED_URI + 'default/subscriptions'
+    post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username,
+                             'subscriptions')
+
     return self.Post(subscription_entry, post_uri,
                      converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
 
-  def DeleteSubscription(self, subscription_uri):
-    """Delete a subscription from the currently authenticated user's account
+  def AddSubscriptionToQuery(self, query, my_username = 'default'):
+    """Add a new subscription to a specific keyword query to the currently
+    authenticated user's account.
 
     Needs authentication
 
-    Arguments:
-        subscription_uri: The uri of a subscription
+    Args:
+      query: A string representing the keyword query to subscribe to.
+      my_username: An optional string representing the username of the user
+          that is to be subscribed. Defaults to currently authenticated user.
+
+    Returns:
+        A new YouTubeSubscriptionEntry if successful.
+    """
+    subscription_category = atom.Category(
+        scheme=YOUTUBE_SUBSCRIPTION_CATEGORY_SCHEME,
+        term='query')
+    subscription_query_string = gdata.youtube.QueryString(text=query)
+
+    subscription_entry = gdata.youtube.YouTubeSubscriptionEntry(
+        category=subscription_category,
+        query_string=subscription_query_string)
+
+    post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username,
+                             'subscriptions')
+
+    return self.Post(subscription_entry, post_uri,
+                     converter=gdata.youtube.YouTubeSubscriptionEntryFromString)
+
+
+
+  def DeleteSubscription(self, subscription_uri):
+    """Delete a subscription from the currently authenticated user's account.
+
+    Needs authentication.
+
+    Args:
+      subscription_uri: A string representing the URI of the subscription that
+          is to be deleted.
 
     Returns:
-        True if successfully deleted
+      True if deleted successfully.
     """
     return self.Delete(subscription_uri)
 
   def AddContact(self, contact_username, my_username='default'):
     """Add a new contact to the currently authenticated user's contact feed.
 
-    Needs authentication
+    Needs authentication.
 
-    Arguments:
-        contact_username: The username of the contact that you wish to add
-        my_username: (optional) The username of the contact feed
+    Args:
+      contact_username: A string representing the username of the contact
+          that you wish to add.
+      my_username: An optional string representing the username to whose
+          contact the new contact is to be added.
 
     Returns:
-        A YouTubeContactEntry if added successfully
+        A YouTubeContactEntry if added successfully.
     """
     contact_category = atom.Category(
         scheme = 'http://gdata.youtube.com/schemas/2007/contact.cat',
@@ -751,89 +1183,118 @@
     contact_entry = gdata.youtube.YouTubeContactEntry(
         category=contact_category,
         username=contact_username)
-    contact_post_uri = YOUTUBE_USER_FEED_URI + my_username + '/contacts'
+
+    contact_post_uri = '%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username,
+                                     'contacts')
+
     return self.Post(contact_entry, contact_post_uri,
                      converter=gdata.youtube.YouTubeContactEntryFromString)
 
   def UpdateContact(self, contact_username, new_contact_status, 
                     new_contact_category, my_username='default'):
-    """Update a contact, providing a new status and a new category
+    """Update a contact, providing a new status and a new category.
 
-    Needs authentication
-
-    Arguments:
-        contact_username: The username of the contact to be updated
-        new_contact_status: New status, either 'accepted' or 'rejected'
-        new_contact_category: New category for the contact, either 'Friends' or
-            'Family'
-        my_username: (optional) Username of the user whose contact feed we are 
-            modifying. Defaults to the currently authenticated user
+    Needs authentication.
 
-    Returns:
-        A YouTubeContactEntry if updated succesfully
+    Args:
+      contact_username: A string representing the username of the contact
+          that is to be updated.
+      new_contact_status: A string representing the new status of the contact.
+          This can either be set to 'accepted' or 'rejected'.
+      new_contact_category: A string representing the new category for the
+          contact, either 'Friends' or 'Family'.
+      my_username: An optional string representing the username of the user
+          whose contact feed we are modifying. Defaults to the currently
+          authenticated user.
+
+    Returns:
+      A YouTubeContactEntry if updated succesfully.
+
+    Raises:
+      YouTubeError: New contact status must be within the accepted values. Or
+          new contact category must be within the accepted categories.
     """
     if new_contact_status not in YOUTUBE_CONTACT_STATUS:
-      raise YouTubeError('New contact status must be one of ' +
-                         ' '.join(YOUTUBE_CONTACT_STATUS))
+      raise YouTubeError('New contact status must be one of %s' %
+                          (' '.join(YOUTUBE_CONTACT_STATUS)))
     if new_contact_category not in YOUTUBE_CONTACT_CATEGORY:
-      raise YouTubeError('New contact category must be one of ' +
-                         ' '.join(YOUTUBE_CONTACT_CATEGORY))
+      raise YouTubeError('New contact category must be one of %s' %
+                         (' '.join(YOUTUBE_CONTACT_CATEGORY)))
 
     contact_category = atom.Category(
         scheme='http://gdata.youtube.com/schemas/2007/contact.cat',
         term=new_contact_category)
+
     contact_status = gdata.youtube.Status(text=new_contact_status)
     contact_entry = gdata.youtube.YouTubeContactEntry(
         category=contact_category,
         status=contact_status)
-    contact_put_uri = (YOUTUBE_USER_FEED_URI + my_username + '/contacts/' +
-                       contact_id)
+
+    contact_put_uri = '%s/%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username,
+                                       'contacts', contact_username)
+
     return self.Put(contact_entry, contact_put_uri,
                     converter=gdata.youtube.YouTubeContactEntryFromString)
 
   def DeleteContact(self, contact_username, my_username='default'):
-    """Delete a contact from a users contact feed
+    """Delete a contact from a users contact feed.
 
-    Needs authentication
+    Needs authentication.
 
-    Arguments:
-        contact_username: Username of the contact to be deleted
-        my_username: (optional) Username of the users contact feed that is to 
-            be modified. Defaults to the currently authenticated user
+    Args:
+      contact_username: A string representing the username of the contact
+          that is to be deleted.
+      my_username: An optional string representing the username of the user's
+          contact feed from which to delete the contact. Defaults to the
+          currently authenticated user.
 
     Returns:
-        True if the contact was deleted successfully
+      True if the contact was deleted successfully
     """
-    contact_edit_uri = (YOUTUBE_USER_FEED_URI + my_username +
-                        '/contacts/' + contact_username)
+    contact_edit_uri = '%s/%s/%s/%s' % (YOUTUBE_USER_FEED_URI, my_username,
+                                        'contacts', contact_username)
     return self.Delete(contact_edit_uri)
 
-
   def _GetDeveloperKey(self):
-    """Getter for Developer Key property"""
-    if '_developer_key' in self.keys():
-      return self._developer_key
+    """Getter for Developer Key property.
+
+    Returns:
+      If the developer key has been set, a string representing the developer key
+          is returned or None.
+    """
+    if 'X-GData-Key' in self.additional_headers:
+      return self.additional_headers['X-GData-Key'][4:]
     else:
       return None
 
   def _SetDeveloperKey(self, developer_key):
-    """Setter for Developer Key property"""
-    self._developer_key = developer_key
+    """Setter for Developer Key property.
+    
+    Sets the developer key in the 'X-GData-Key' header. The actual value that
+        is set is 'key=' plus the developer_key that was passed.
+    """
     self.additional_headers['X-GData-Key'] = 'key=' + developer_key
 
   developer_key = property(_GetDeveloperKey, _SetDeveloperKey,
                            doc="""The Developer Key property""")
 
   def _GetClientId(self):
-    """Getter for Client Id property"""
-    if '_client_id' in self.keys():
-      return self._client_id
+    """Getter for Client Id property.
+
+    Returns:
+      If the client_id has been set, a string representing it is returned
+          or None.
+    """
+    if 'X-Gdata-Client' in self.additional_headers:
+      return self.additional_headers['X-Gdata-Client']
     else:
       return None
 
   def _SetClientId(self, client_id):
-    """Setter for Client Id property"""
-    self._client_id = client_id
+    """Setter for Client Id property.
+
+    Sets the 'X-Gdata-Client' header.
+    """
     self.additional_headers['X-Gdata-Client'] = client_id
 
   client_id = property(_GetClientId, _SetClientId,
@@ -843,21 +1304,37 @@
     """Performs a query and returns a resulting feed or entry.
 
     Args:
-      feed: string The feed which is to be queried
+      uri: A string representing the URI of the feed that is to be queried.
 
     Returns:
-      On success, a tuple in the form
+      On success, a tuple in the form:
       (boolean succeeded=True, ElementTree._Element result)
-      On failure, a tuple in the form
-      (boolean succeeded=False, {'status': HTTP status code from server, 
-                                 'reason': HTTP reason from the server, 
+      On failure, a tuple in the form:
+      (boolean succeeded=False, {'status': HTTP status code from server,
+                                 'reason': HTTP reason from the server,
                                  'body': HTTP body of the server's response})
     """
-
     result = self.Get(uri)
     return result
 
   def YouTubeQuery(self, query):
+    """Performs a YouTube specific query and returns a resulting feed or entry.
+
+    Args:
+      query: A Query object or one if its sub-classes (YouTubeVideoQuery,
+          YouTubeUserQuery or YouTubePlaylistQuery).
+
+    Returns:
+      Depending on the type of Query object submitted returns either a
+          YouTubeVideoFeed, a YouTubeUserFeed, a YouTubePlaylistFeed. If the
+          Query object provided was not YouTube-related, a tuple is returned.
+          On success the tuple will be in this form:
+          (boolean succeeded=True, ElementTree._Element result)
+          On failure, the tuple will be in this form:
+          (boolean succeeded=False, {'status': HTTP status code from server,
+                                     'reason': HTTP reason from the server,
+                                     'body': HTTP body of the server response})
+    """
     result = self.Query(query.ToUri())
     if isinstance(query, YouTubeVideoQuery):
       return gdata.youtube.YouTubeVideoFeedFromString(result.ToString())
@@ -870,6 +1347,41 @@
 
 class YouTubeVideoQuery(gdata.service.Query):
 
+  """Subclasses gdata.service.Query to represent a YouTube Data API query.
+
+  Attributes are set dynamically via properties. Properties correspond to
+  the standard Google Data API query parameters with YouTube Data API
+  extensions. Please refer to the API documentation for details.
+
+  Attributes:
+    vq: The vq parameter, which is only supported for video feeds, specifies a
+        search query term. Refer to API documentation for further details.
+    orderby: The orderby parameter, which is only supported for video feeds,
+        specifies the value that will be used to sort videos in the search
+        result set. Valid values for this parameter are relevance, published,
+        viewCount and rating.
+    time: The time parameter, which is only available for the top_rated,
+        top_favorites, most_viewed, most_discussed, most_linked and
+        most_responded standard feeds, restricts the search to videos uploaded
+        within the specified time. Valid values for this parameter are today
+        (1 day), this_week (7 days), this_month (1 month) and all_time.
+        The default value for this parameter is all_time.
+    format: The format parameter specifies that videos must be available in a
+        particular video format. Refer to the API documentation for details.
+    racy: The racy parameter allows a search result set to include restricted
+        content as well as standard content. Valid values for this parameter
+        are include and exclude. By default, restricted content is excluded.
+    lr: The lr parameter restricts the search to videos that have a title,
+        description or keywords in a specific language. Valid values for the lr
+        parameter are ISO 639-1 two-letter language codes.
+    restriction: The restriction parameter identifies the IP address that
+        should be used to filter videos that can only be played in specific
+        countries.
+    location: A string of geo coordinates. Note that this is not used when the
+        search is performed but rather to filter the returned videos for ones
+        that match to the location entered.
+  """
+
   def __init__(self, video_id=None, feed_type=None, text_query=None,
                params=None, categories=None):
 
@@ -883,33 +1395,9 @@
 
     gdata.service.Query.__init__(self, feed, text_query=text_query,
                                  params=params, categories=categories)
-
-  def _GetStartMin(self):
-    if 'start-min' in self.keys():
-      return self['start-min']
-    else:
-      return None
-
-  def _SetStartMin(self, val):
-    self['start-min'] = val
-
-  start_min = property(_GetStartMin, _SetStartMin,
-                       doc="""The start-min query parameter""")
-
-  def _GetStartMax(self):
-    if 'start-max' in self.keys():
-      return self['start-max']
-    else:
-      return None
-
-  def _SetStartMax(self, val):
-    self['start-max'] = val
-
-  start_max = property(_GetStartMax, _SetStartMax,
-                       doc="""The start-max query parameter""")
-
+ 
   def _GetVideoQuery(self):
-    if 'vq' in self.keys():
+    if 'vq' in self:
       return self['vq']
     else:
       return None
@@ -921,29 +1409,30 @@
                 doc="""The video query (vq) query parameter""")
 
   def _GetOrderBy(self):
-    if 'orderby' in self.keys():
+    if 'orderby' in self:
       return self['orderby']
     else:
       return None
 
   def _SetOrderBy(self, val):
     if val not in YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS:
-      raise YouTubeError('OrderBy must be one of: %s ' %
-                         ' '.join(YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS))
+      if val.startswith('relevance_lang_') is False:
+        raise YouTubeError('OrderBy must be one of: %s ' %
+                           ' '.join(YOUTUBE_QUERY_VALID_ORDERBY_PARAMETERS))
     self['orderby'] = val
 
   orderby = property(_GetOrderBy, _SetOrderBy,
                      doc="""The orderby query parameter""")
 
   def _GetTime(self):
-    if 'time' in self.keys():
+    if 'time' in self:
       return self['time']
     else:
       return None
 
   def _SetTime(self, val):
     if val not in YOUTUBE_QUERY_VALID_TIME_PARAMETERS:
-      raise YouTubeError('Time must be one of: %s ' % 
+      raise YouTubeError('Time must be one of: %s ' %
                          ' '.join(YOUTUBE_QUERY_VALID_TIME_PARAMETERS))
     self['time'] = val
 
@@ -951,14 +1440,14 @@
                   doc="""The time query parameter""")
 
   def _GetFormat(self):
-    if 'format' in self.keys():
+    if 'format' in self:
       return self['format']
     else:
       return None
 
   def _SetFormat(self, val):
     if val not in YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS:
-      raise YouTubeError('Format must be one of: %s ' % 
+      raise YouTubeError('Format must be one of: %s ' %
                          ' '.join(YOUTUBE_QUERY_VALID_FORMAT_PARAMETERS))
     self['format'] = val
 
@@ -966,36 +1455,81 @@
                     doc="""The format query parameter""")
 
   def _GetRacy(self):
-    if 'racy' in self.keys():
+    if 'racy' in self:
       return self['racy']
     else:
       return None
 
   def _SetRacy(self, val):
     if val not in YOUTUBE_QUERY_VALID_RACY_PARAMETERS:
-      raise YouTubeError('Racy must be one of: %s ' % 
+      raise YouTubeError('Racy must be one of: %s ' %
                          ' '.join(YOUTUBE_QUERY_VALID_RACY_PARAMETERS))
     self['racy'] = val
 
   racy = property(_GetRacy, _SetRacy, 
                   doc="""The racy query parameter""")
 
+  def _GetLanguageRestriction(self):
+    if 'lr' in self:
+      return self['lr']
+    else:
+      return None
+
+  def _SetLanguageRestriction(self, val):
+    self['lr'] = val
+
+  lr = property(_GetLanguageRestriction, _SetLanguageRestriction,
+                doc="""The lr (language restriction) query parameter""")
+
+  def _GetIPRestriction(self):
+    if 'restriction' in self:
+      return self['restriction']
+    else:
+      return None
+
+  def _SetIPRestriction(self, val):
+    self['restriction'] = val
+
+  restriction = property(_GetIPRestriction, _SetIPRestriction,
+                         doc="""The restriction query parameter""")
+
+  def _GetLocation(self):
+    if 'location' in self:
+      return self['location']
+    else:
+      return None
+
+  def _SetLocation(self, val):
+    self['location'] = val
+
+  location = property(_GetLocation, _SetLocation,
+                      doc="""The location query parameter""")
+
+
+
 class YouTubeUserQuery(YouTubeVideoQuery):
 
+  """Subclasses YouTubeVideoQuery to perform user-specific queries.
+
+  Attributes are set dynamically via properties. Properties correspond to
+  the standard Google Data API query parameters with YouTube Data API
+  extensions.
+  """
+
   def __init__(self, username=None, feed_type=None, subscription_id=None,
                text_query=None, params=None, categories=None):
 
     uploads_favorites_playlists = ('uploads', 'favorites', 'playlists')
 
     if feed_type is 'subscriptions' and subscription_id and username:
-      feed = "http://%s/feeds/users/%s/%s/%s"; % (
-          YOUTUBE_SERVER, username, feed_type, subscription_id)
+      feed = "http://%s/feeds/users/%s/%s/%s"; % (YOUTUBE_SERVER, username,
+                                                 feed_type, subscription_id)
     elif feed_type is 'subscriptions' and not subscription_id and username:
-      feed = "http://%s/feeds/users/%s/%s"; % (
-          YOUTUBE_SERVER, username, feed_type)
+      feed = "http://%s/feeds/users/%s/%s"; % (YOUTUBE_SERVER, username,
+                                              feed_type)
     elif feed_type in uploads_favorites_playlists:
-      feed = "http://%s/feeds/users/%s/%s"; % (
-          YOUTUBE_SERVER, username, feed_type)
+      feed = "http://%s/feeds/users/%s/%s"; % (YOUTUBE_SERVER, username, 
+                                              feed_type)
     else:
       feed = "http://%s/feeds/users"; % (YOUTUBE_SERVER)
 
@@ -1005,6 +1539,13 @@
 
 class YouTubePlaylistQuery(YouTubeVideoQuery):
 
+  """Subclasses YouTubeVideoQuery to perform playlist-specific queries.
+
+  Attributes are set dynamically via properties. Properties correspond to
+  the standard Google Data API query parameters with YouTube Data API
+  extensions.
+  """
+
   def __init__(self, playlist_id, text_query=None, params=None,
                categories=None):
     if playlist_id:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]