NEW: Support for specifying the SSL protocol version (Python v2) (#9)

* Support for specifying the SSL protocol version (Python v2)

This fixes #329.
diff --git a/python2/httplib2/__init__.py b/python2/httplib2/__init__.py
index a64d8b4..b6307d3 100644
--- a/python2/httplib2/__init__.py
+++ b/python2/httplib2/__init__.py
@@ -20,7 +20,8 @@
                     "Jonathan Feinberg",
                     "Blair Zajac",
                     "Sam Ruby",
-                    "Louis Nyffenegger"]
+                    "Louis Nyffenegger",
+                    "Alex Yu"]
 __license__ = "MIT"
 __version__ = "0.9.2"
 
@@ -68,16 +69,16 @@
     import ssl # python 2.6
     ssl_SSLError = ssl.SSLError
     def _ssl_wrap_socket(sock, key_file, cert_file,
-                         disable_validation, ca_certs):
+                         disable_validation, ca_certs, ssl_version):
         if disable_validation:
             cert_reqs = ssl.CERT_NONE
         else:
             cert_reqs = ssl.CERT_REQUIRED
-        # We should be specifying SSL version 3 or TLS v1, but the ssl module
-        # doesn't expose the necessary knobs. So we need to go with the default
-        # of SSLv23.
+        if ssl_version is None:
+            ssl_version = ssl.PROTOCOL_SSLv23
         return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file,
-                               cert_reqs=cert_reqs, ca_certs=ca_certs)
+                               cert_reqs=cert_reqs, ca_certs=ca_certs,
+                               ssl_version=ssl_version)
 except (AttributeError, ImportError):
     ssl_SSLError = None
     def _ssl_wrap_socket(sock, key_file, cert_file,
@@ -938,7 +939,8 @@
     """
     def __init__(self, host, port=None, key_file=None, cert_file=None,
                  strict=None, timeout=None, proxy_info=None,
-                 ca_certs=None, disable_ssl_certificate_validation=False):
+                 ca_certs=None, disable_ssl_certificate_validation=False,
+                 ssl_version=None):
         httplib.HTTPSConnection.__init__(self, host, port=port,
                                          key_file=key_file,
                                          cert_file=cert_file, strict=strict)
@@ -949,6 +951,7 @@
         self.ca_certs = ca_certs
         self.disable_ssl_certificate_validation = \
                 disable_ssl_certificate_validation
+        self.ssl_version = ssl_version
 
     # The following two methods were adapted from https_wrapper.py, released
     # with the Google Appengine SDK at
@@ -1033,7 +1036,8 @@
                 sock.connect((self.host, self.port))
                 self.sock =_ssl_wrap_socket(
                     sock, self.key_file, self.cert_file,
-                    self.disable_ssl_certificate_validation, self.ca_certs)
+                    self.disable_ssl_certificate_validation, self.ca_certs,
+                    self.ssl_version)
                 if self.debuglevel > 0:
                     print "connect: (%s, %s)" % (self.host, self.port)
                     if use_proxy:
@@ -1155,7 +1159,8 @@
     """
     def __init__(self, cache=None, timeout=None,
                  proxy_info=proxy_info_from_environment,
-                 ca_certs=None, disable_ssl_certificate_validation=False):
+                 ca_certs=None, disable_ssl_certificate_validation=False,
+                 ssl_version=None):
         """If 'cache' is a string then it is used as a directory name for
         a disk cache. Otherwise it must be an object that supports the
         same interface as FileCache.
@@ -1178,11 +1183,14 @@
 
         If disable_ssl_certificate_validation is true, SSL cert validation will
         not be performed.
+
+        By default, ssl.PROTOCOL_SSLv23 will be used for the ssl version.
         """
         self.proxy_info = proxy_info
         self.ca_certs = ca_certs
         self.disable_ssl_certificate_validation = \
                 disable_ssl_certificate_validation
+        self.ssl_version = ssl_version
 
         # Map domain name to an httplib connection
         self.connections = {}
@@ -1478,14 +1486,16 @@
                                 proxy_info=proxy_info,
                                 ca_certs=self.ca_certs,
                                 disable_ssl_certificate_validation=
-                                        self.disable_ssl_certificate_validation)
+                                        self.disable_ssl_certificate_validation,
+                                        ssl_version=self.ssl_version)
                     else:
                         conn = self.connections[conn_key] = connection_type(
                                 authority, timeout=self.timeout,
                                 proxy_info=proxy_info,
                                 ca_certs=self.ca_certs,
                                 disable_ssl_certificate_validation=
-                                        self.disable_ssl_certificate_validation)
+                                        self.disable_ssl_certificate_validation,
+                                ssl_version=self.ssl_version)
                 else:
                     conn = self.connections[conn_key] = connection_type(
                             authority, timeout=self.timeout,
diff --git a/python2/httplib2/test/other_cacerts.txt b/python2/httplib2/test/other_cacerts.txt
index 360954a..b2c2488 100644
--- a/python2/httplib2/test/other_cacerts.txt
+++ b/python2/httplib2/test/other_cacerts.txt
@@ -1,70 +1,19 @@
-# Certifcate Authority certificates for validating SSL connections.
-#
-# This file contains PEM format certificates generated from
-# http://mxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt
-#
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (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.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is the Netscape security libraries.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1994-2000
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-
-
-Comodo CA Limited, CN=Trusted Certificate Services
-==================================================
-
 -----BEGIN CERTIFICATE-----
-MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
-MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
-GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
-aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
-MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
-BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
-VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
-AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
-fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
-TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
-fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
-1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
-kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
-A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
-VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
-ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
-dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
-Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
-HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
-pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
-jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
-xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
-dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
+MIIDBzCCAe+gAwIBAgIJAIw94zvO7fk1MA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
+BAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNjA2MDQwMjMxMTRaFw0yNjA2MDIwMjMx
+MTRaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAK3YNcDIwK/wlTa0/iBARvDFOncQ6Jkk+Ymql1HXny7v
+mWPFWeLXEW+Zw1NrQEx/SIUGvxpRA+QyhTOhu2Gcwvtqilix/dHgaKgqWEcRYu8m
+L70uVDPVgB/kfNI8bpXM1Mz8Crjo0tHw5oUSD3wny8SyT6CYlXVmF923L8c2zdN9
+n9blFgYwxBq2+q+mqOiDErMFbwHES8FNBSWGBXdE1xjBdITtlfeHezmJhj/ylPW1
+7v8HInsv/WqU9DcJYlFxSnK0SZCLFBM/31Ez8O1gCfMlDUFvJoo59GyFqukUjuO1
+uB85wpu27gtcLm/J9X1Md71IxbDupV7a0dDoTvbhO4kCAwEAAaNQME4wHQYDVR0O
+BBYEFIHgAmwppZSKLz2peyFSO2kwVobNMB8GA1UdIwQYMBaAFIHgAmwppZSKLz2p
+eyFSO2kwVobNMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJxz+AU/
+Iq8fMEStJ0BgPP1N86W9Jpb7aPMFCYTEZ+nd8hFPhPs4//55J0yIve+1I43MNFFz
+yflwwCzrIIhZdkvbsyea6CmlTo4jBc4+ihaDGobYnoNzFhavC47n5kYqJ8Ikyb2W
+OMrmNRiaTeSBl0wQmftnnQCbonenjmE1LDuJtE6bCwfFjfLbMxwdWtp/ymOlXsb5
+80XcWwcqc12UHWexYwHFzEJmDfncak/8tjHBsLWMJg5p2sVTY9kVt7TYgSIl+mFb
+4WVGrqZd2uTlJkRQQ4pCl+D+PKwadHuV6YI7oxkeajjcHCgbK/ANwW28MXYho6t6
+aWVIN4bWHrZ38kE=
 -----END CERTIFICATE-----
diff --git a/python2/httplib2test.py b/python2/httplib2test.py
index d9bfdb6..0536512 100755
--- a/python2/httplib2test.py
+++ b/python2/httplib2test.py
@@ -511,25 +511,6 @@
         self.assertEqual(200, response.status)
         self.assertNotEqual(None, response.previous)
 
-    def testSslCertValidation(self):
-        if sys.version_info >= (2, 6):
-            # Test that we get an ssl.SSLError when specifying a non-existent CA
-            # certs file.
-            http = httplib2.Http(ca_certs='/nosuchfile')
-            self.assertRaises(ssl.SSLError,
-                    http.request, "https://www.google.com/", "GET")
-
-            # Test that we get a SSLHandshakeError if we try to access
-            # https;//www.google.com, using a CA cert file that doesn't contain
-            # the CA Gogole uses (i.e., simulating a cert that's not signed by a
-            # trusted CA).
-            other_ca_certs = os.path.join(
-                    os.path.dirname(os.path.abspath(httplib2.__file__ )),
-                    "test", "other_cacerts.txt")
-            http = httplib2.Http(ca_certs=other_ca_certs)
-            self.assertRaises(httplib2.SSLHandshakeError,
-                    http.request, "https://www.google.com/", "GET")
-
     def testSslCertValidationDoubleDots(self):
         pass
         # No longer a valid test.
diff --git a/python2/ssl_protocol_test.py b/python2/ssl_protocol_test.py
new file mode 100755
index 0000000..bac84c0
--- /dev/null
+++ b/python2/ssl_protocol_test.py
@@ -0,0 +1,57 @@
+"""Tests for SSL handling in httplib2."""
+
+import httplib2
+import os
+import ssl
+import sys
+import unittest
+
+
+class TestSslProtocol(unittest.TestCase):
+
+  def testSslCertValidationWithInvalidCaCert(self):
+    if sys.version_info >= (2, 6):
+      http = httplib2.Http(ca_certs='/nosuchfile')
+      if sys.version_info >= (2, 7):
+        with self.assertRaises(IOError):
+          http.request('https://www.google.com/', 'GET')
+      else:
+        self.assertRaises(
+            ssl.SSLError, http.request, 'https://www.google.com/', 'GET')
+
+  def testSslCertValidationWithSelfSignedCaCert(self):
+    if sys.version_info >= (2, 7):
+      other_ca_certs = os.path.join(
+          os.path.dirname(os.path.abspath(httplib2.__file__ )), 'test',
+          'other_cacerts.txt')
+      http = httplib2.Http(ca_certs=other_ca_certs)
+      if sys.platform != 'darwin':
+        with self.assertRaises(httplib2.SSLHandshakeError):
+          http.request('https://www.google.com/', 'GET')
+
+  def testSslProtocolTlsV1AndShouldPass(self):
+    http = httplib2.Http(ssl_version=ssl.PROTOCOL_TLSv1)
+    urls = ['https://www.amazon.com',
+            'https://www.apple.com',
+            'https://www.twitter.com']
+    for url in urls:
+      if sys.version_info >= (2, 7):
+        self.assertIsNotNone(http.request(uri=url))
+
+  def testSslProtocolV3AndShouldFailDueToPoodle(self):
+    http = httplib2.Http(ssl_version=ssl.PROTOCOL_SSLv3)
+    urls = ['https://www.amazon.com',
+            'https://www.apple.com',
+            'https://www.twitter.com']
+    for url in urls:
+      if sys.version_info >= (2, 7):
+        with self.assertRaises(httplib2.SSLHandshakeError):
+          http.request(url)
+        try:
+          http.request(url)
+        except httplib2.SSLHandshakeError as e:
+          self.assertTrue('sslv3 alert handshake failure' in str(e))
+
+
+if __name__ == '__main__':
+  unittest.main()