diff --git a/.gitignore b/.gitignore index 20ec5ddec..ef20b6390 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ htmlcov build dist shotgun_api3.egg-info + +# IDE +.idea/ diff --git a/shotgun_api3/lib/httplib2/__init__.py b/shotgun_api3/lib/httplib2/__init__.py index 19e7cff11..00cf50f0a 100644 --- a/shotgun_api3/lib/httplib2/__init__.py +++ b/shotgun_api3/lib/httplib2/__init__.py @@ -76,7 +76,12 @@ def _ssl_wrap_socket(sock, key_file, cert_file, # 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. - return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file, + if sys.platform == "cli": + # IronPython doesn't correctly fall back to TLS1 which the SG Server uses so we are hard coding it + return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file, + cert_reqs=cert_reqs, ca_certs=ca_certs, ssl_version=ssl.PROTOCOL_TLSv1) + else: + return ssl.wrap_socket(sock, keyfile=key_file, certfile=cert_file, cert_reqs=cert_reqs, ca_certs=ca_certs) except (AttributeError, ImportError): ssl_SSLError = None diff --git a/shotgun_api3/lib/httplib2/iri2uri.py b/shotgun_api3/lib/httplib2/iri2uri.py index d88c91fdf..d4ff76ae2 100644 --- a/shotgun_api3/lib/httplib2/iri2uri.py +++ b/shotgun_api3/lib/httplib2/iri2uri.py @@ -12,9 +12,9 @@ __history__ = """ """ +import sys import urlparse - # Convert an IRI to a URI following the rules in RFC 3987 # # The characters we need to enocde and escape are defined in the spec: @@ -68,7 +68,12 @@ def iri2uri(uri): the IRI before passing it into the function.""" if isinstance(uri ,unicode): (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri) - authority = authority.encode('idna') + + # IronPython before 2.7.5b3 doesn't support idna encoding so we are using utf-8 instead + # see also: https://ironpython.codeplex.com/workitem/34651 + encoding_scheme = 'utf-8' if (sys.platform == "cli" and sys.version_info < (2, 7, 5)) else 'idna' + authority = authority.encode(encoding_scheme) + # For each character in 'ucschar' or 'iprivate' # 1. encode as utf-8 # 2. then %-encode each octet of that utf-8 @@ -76,35 +81,35 @@ def iri2uri(uri): uri = "".join([encode(c) for c in uri]) return uri -if __name__ == "__main__": - import unittest - - class Test(unittest.TestCase): - - def test_uris(self): - """Test that URIs are invariant under the transformation.""" - invariant = [ - u"ftp://ftp.is.co.za/rfc/rfc1808.txt", - u"http://www.ietf.org/rfc/rfc2396.txt", - u"ldap://[2001:db8::7]/c=GB?objectClass?one", - u"mailto:John.Doe@example.com", - u"news:comp.infosystems.www.servers.unix", - u"tel:+1-816-555-1212", - u"telnet://192.0.2.16:80/", - u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ] - for uri in invariant: - self.assertEqual(uri, iri2uri(uri)) - - def test_iri(self): - """ Test that the right type of escaping is done for each part of the URI.""" - self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}")) - self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}")) - self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}")) - self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}")) - self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")) - self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))) - self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8'))) - - unittest.main() +# if __name__ == "__main__": +# import unittest +# +# class Test(unittest.TestCase): +# +# def test_uris(self): +# """Test that URIs are invariant under the transformation.""" +# invariant = [ +# u"ftp://ftp.is.co.za/rfc/rfc1808.txt", +# u"http://www.ietf.org/rfc/rfc2396.txt", +# u"ldap://[2001:db8::7]/c=GB?objectClass?one", +# u"mailto:John.Doe@example.com", +# u"news:comp.infosystems.www.servers.unix", +# u"tel:+1-816-555-1212", +# u"telnet://192.0.2.16:80/", +# u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ] +# for uri in invariant: +# self.assertEqual(uri, iri2uri(uri)) +# +# def test_iri(self): +# """ Test that the right type of escaping is done for each part of the URI.""" +# self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}")) +# self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}")) +# self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}")) +# self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}")) +# self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")) +# self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))) +# self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8'))) +# +# unittest.main() diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index c497c3e0b..9cbefa006 100755 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -34,6 +34,7 @@ import cookielib # used for attachment upload import cStringIO # used for attachment upload import datetime +import httplib import logging import mimetools # used for attachment upload import os @@ -47,6 +48,7 @@ import urllib2 # used for image upload import urlparse import shutil # used for attachment download +import socket # use relative import for versions >=2.5 and package import for python versions <2.5 if (sys.version_info[0] > 2) or (sys.version_info[0] == 2 and sys.version_info[1] >= 6): @@ -57,8 +59,8 @@ from sg_24 import * # mimetypes imported in version specific imports -mimetypes.add_type('video/webm','.webm') # webm and mp4 seem to be missing -mimetypes.add_type('video/mp4', '.mp4') # from some OS/distros +# mimetypes.add_type('video/webm','.webm') # webm and mp4 seem to be missing +# mimetypes.add_type('video/mp4', '.mp4') # from some OS/distros LOG = logging.getLogger("shotgun_api3") LOG.setLevel(logging.WARN) @@ -68,7 +70,8 @@ NO_SSL_VALIDATION = False try: - import ssl + import ssl + NO_SSL_VALIDATION = sys.platform == "cli" except ImportError, e: if "SHOTGUN_FORCE_CERTIFICATE_VALIDATION" in os.environ: raise ImportError("%s. SHOTGUN_FORCE_CERTIFICATE_VALIDATION environment variable prevents " @@ -1521,6 +1524,12 @@ def upload(self, entity_type, entity_id, path, field_name=None, raise ShotgunError("Could not upload file successfully, but "\ "not sure why.\nPath: %s\nUrl: %s\nError: %s" % ( path, url, str(result))) + finally: + if is_thumbnail: + params["thumb_image"].close() + else: + params["file"].close() + attachment_id = int(str(result).split(":")[1].split("\n")[0]) return attachment_id @@ -2582,12 +2591,43 @@ def _dict_to_list(self, d, key_name="field_name", value_name="value"): # Helpers from the previous API, left as is. # Based on http://code.activestate.com/recipes/146306/ -class FormPostHandler(urllib2.BaseHandler): +class FormPostHandler(urllib2.AbstractHTTPHandler): """ Handler for multipart form data """ handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first + # Python 2.6's urllib2 does not allow you to select the TLS dialect, + # and by default uses a SSLv23 compatibility negotiation implementation. + # Besides being vulnerable to POODLE, the OSX implementation doesn't + # work correctly, failing to connect to servers that respond only to + # TLS1.0+. These classes help set up TLS support for urllib2. + # Based on https://gist.github.com/flandr/74be22d1c3d7c1dfefdd + class TLS1Connection(httplib.HTTPSConnection): + """Like HTTPSConnection but more specific""" + def __init__(self, host, **kwargs): + httplib.HTTPSConnection.__init__(self, host, **kwargs) + + def connect(self): + """Overrides HTTPSConnection.connect to specify TLS version""" + # Standard implementation from HTTPSConnection, which is not + # designed for extension, unfortunately + sock = socket.create_connection((self.host, self.port), + self.timeout, self.source_address) + if getattr(self, '_tunnel_host', None): + self.sock = sock + self._tunnel() + + # This is the only difference; default wrap_socket uses SSLv23 + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1) + + def https_open(self, req): + # IronPython doesn't correctly fall back to using TLS1 which the SG Server uses so we are hard coding it here + if sys.platform == "cli": + return self.do_open(FormPostHandler.TLS1Connection, req) + else: + return self.do_open(httplib.HTTPSConnection, req) + def http_request(self, request): data = request.get_data() if data is not None and not isinstance(data, basestring):