From 258420f41049d92fc0b59b9263c8381bfe513432 Mon Sep 17 00:00:00 2001
From: Christian Schwede <christian.schwede@enovance.com>
Date: Tue, 20 May 2014 11:41:03 +0000
Subject: [PATCH] Fix Python3 bugs

This patch fixes three issues that were found running
functional tests with Python 3:

1. python-requests sets 'application/x-www-form-urlencoded' as
   content-type if the input is not a string object and no
   content-type is given.

2. Encoding of the headers is now only done if required. This
   prevents comparisons between unencoded headers and encoded
   prefixes and avoids unnecessary forloop-iterations.
   One unittest was extended to ensure it works for unencoded
   and encoded headers with or without the prefix.

3. Functional tests recently switched to using byte data for
   testing, thus the comparison needs to be a byte object as well.

Change-Id: I035f8b4b9c9ccdc79820b907770a48f86d0343b4
---
 swiftclient/client.py                | 17 ++++++++++-------
 tests/functional/test_swiftclient.py |  2 +-
 tests/unit/test_swiftclient.py       | 20 +++++++++++++++++++-
 3 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/swiftclient/client.py b/swiftclient/client.py
index 5494f34d..a7d0dafb 100644
--- a/swiftclient/client.py
+++ b/swiftclient/client.py
@@ -186,10 +186,12 @@ class HTTPConnection:
         for header, value in items:
             value = encode_utf8(value)
             header = header.lower()
-            for target_type in 'container', 'account', 'object':
-                prefix = 'x-%s-meta-' % target_type
-                if header.startswith(prefix):
-                    header = encode_utf8(header)
+            if isinstance(header, six.string_types):
+                for target_type in 'container', 'account', 'object':
+                    prefix = 'x-%s-meta-' % target_type
+                    if header.startswith(prefix):
+                        header = encode_utf8(header)
+                        break
             ret[header] = value
         return ret
 
@@ -899,9 +901,8 @@ def put_object(url, token=None, container=None, name=None, contents=None,
     :param chunk_size: chunk size of data to write; it defaults to 65536;
                        used only if the contents object has a 'read'
                        method, e.g. file-like objects, ignored otherwise
-    :param content_type: value to send as content-type header; if None, no
-                         content-type will be set (remote end will likely try
-                         to auto-detect it)
+    :param content_type: value to send as content-type header; if None, an
+                         empty string value will be sent
     :param headers: additional headers to include in the request, if any
     :param http_conn: HTTP connection object (If None, it will create the
                       conn object)
@@ -940,6 +941,8 @@ def put_object(url, token=None, container=None, name=None, contents=None,
                 content_length = int(v)
     if content_type is not None:
         headers['Content-Type'] = content_type
+    else:  # python-requests sets application/x-www-form-urlencoded otherwise
+        headers['Content-Type'] = ''
     if not contents:
         headers['Content-Length'] = '0'
     if hasattr(contents, 'read'):
diff --git a/tests/functional/test_swiftclient.py b/tests/functional/test_swiftclient.py
index 6631d369..3721e518 100644
--- a/tests/functional/test_swiftclient.py
+++ b/tests/functional/test_swiftclient.py
@@ -259,7 +259,7 @@ class TestFunctional(testtools.TestCase):
             self.containername, self.objectname,
             resp_chunk_size=10)
         self.assertTrue(isinstance(body, types.GeneratorType))
-        self.assertEqual(self.test_data, ''.join(body))
+        self.assertEqual(self.test_data, b''.join(body))
 
     def test_post_account(self):
         self.conn.post_account({'x-account-meta-data': 'Something'})
diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py
index 0819475b..2e7a8073 100644
--- a/tests/unit/test_swiftclient.py
+++ b/tests/unit/test_swiftclient.py
@@ -730,6 +730,16 @@ class TestPutObject(MockHttpTest):
         self.assertTrue(request_header['etag'], '1234-5678')
         self.assertTrue(request_header['content-type'], 'text/plain')
 
+    def test_no_content_type(self):
+        conn = c.http_connection(u'http://www.test.com/')
+        resp = MockHttpResponse(status=200)
+        conn[1].getresponse = resp.fake_response
+        conn[1]._request = resp._fake_request
+
+        c.put_object(url='http://www.test.com', http_conn=conn)
+        request_header = resp.requests_params['headers']
+        self.assertEqual(request_header['content-type'], b'')
+
 
 class TestPostObject(MockHttpTest):
 
@@ -746,7 +756,10 @@ class TestPostObject(MockHttpTest):
                 u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91')
         text = u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91'
         headers = {'X-Header1': text,
-                   'X-2': '1', 'X-3': {'a': 'b'}, 'a-b': '.x:yz mn:kl:qr'}
+                   b'X-Header2': 'value',
+                   'X-2': '1', 'X-3': {'a': 'b'}, 'a-b': '.x:yz mn:kl:qr',
+                   'X-Object-Meta-Header-not-encoded': text,
+                   b'X-Object-Meta-Header-encoded': 'value'}
 
         resp = MockHttpResponse()
         conn[1].getresponse = resp.fake_response
@@ -757,6 +770,11 @@ class TestPostObject(MockHttpTest):
         # Test unicode header
         self.assertIn(('x-header1', text.encode('utf8')),
                       resp.buffer)
+        self.assertIn((b'x-object-meta-header-not-encoded',
+                      text.encode('utf8')), resp.buffer)
+        self.assertIn((b'x-object-meta-header-encoded', b'value'),
+                      resp.buffer)
+        self.assertIn((b'x-header2', b'value'), resp.buffer)
 
     def test_server_error(self):
         body = 'c' * 60