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
- From: jstowers svn gnome org
- To: svn-commits-list gnome org
- Subject: 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
- Date: Tue, 17 Mar 2009 09:00:35 +0000 (UTC)
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&max-results=25'/><link rel='next' type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?start-index=26&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&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&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]