Author: tomaz
Date: Thu May 23 19:53:52 2013
New Revision: 1485839

URL: http://svn.apache.org/r1485839
Log:
Fix an issue with double encoding the container name in the CloudFiles
driver upload_object method.

Also properly encode container and object name used in the HTTP request
in the get_container and get_object method. For clarity rename current
 _clean* methods to _encode*.

Fixes LIBCLOUD-328.

Modified:
    libcloud/trunk/CHANGES
    libcloud/trunk/libcloud/storage/drivers/cloudfiles.py
    libcloud/trunk/libcloud/test/storage/test_cloudfiles.py

Modified: libcloud/trunk/CHANGES
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1485839&r1=1485838&r2=1485839&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Thu May 23 19:53:52 2013
@@ -54,6 +54,14 @@ Changes with Apache Libcloud in deveplom
       return value. (LIBCLOUD-326)
       [Andre Merzky, Tomaz Muraus]
 
+ *) Storage
+
+    - Fix an issue with double encoding the container name in the CloudFiles
+      driver upload_object method.
+      Also properly encode container and object name used in the HTTP request
+      in the get_container and get_object method. (LIBCLOUD-328)
+      [Tomaz Muraus]
+
  *) Load Balancer
 
     - Add ex_list_current_usage method to the Rackspace driver.

Modified: libcloud/trunk/libcloud/storage/drivers/cloudfiles.py
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/storage/drivers/cloudfiles.py?rev=1485839&r1=1485838&r2=1485839&view=diff
==============================================================================
--- libcloud/trunk/libcloud/storage/drivers/cloudfiles.py (original)
+++ libcloud/trunk/libcloud/storage/drivers/cloudfiles.py Thu May 23 19:53:52 
2013
@@ -237,7 +237,8 @@ class CloudFilesStorageDriver(StorageDri
         raise LibcloudError('Unexpected status code: %s' % (response.status))
 
     def get_container(self, container_name):
-        response = self.connection.request('/%s' % (container_name),
+        container_name_encoded = self._encode_container_name(container_name)
+        response = self.connection.request('/%s' % (container_name_encoded),
                                            method='HEAD')
 
         if response.status == httplib.NO_CONTENT:
@@ -251,8 +252,11 @@ class CloudFilesStorageDriver(StorageDri
 
     def get_object(self, container_name, object_name):
         container = self.get_container(container_name)
-        response = self.connection.request('/%s/%s' % (container_name,
-                                                       object_name),
+        container_name_encoded = self._encode_container_name(container_name)
+        object_name_encoded = self._encode_container_name(object_name)
+
+        response = self.connection.request('/%s/%s' % (container_name_encoded,
+                                                       object_name_encoded),
                                            method='HEAD')
         if response.status in [httplib.OK, httplib.NO_CONTENT]:
             obj = self._headers_to_object(
@@ -304,9 +308,9 @@ class CloudFilesStorageDriver(StorageDri
         return response.status in [httplib.CREATED, httplib.ACCEPTED]
 
     def create_container(self, container_name):
-        container_name = self._clean_container_name(container_name)
+        container_name_encoded = self._encode_container_name(container_name)
         response = self.connection.request(
-            '/%s' % (container_name), method='PUT')
+            '/%s' % (container_name_encoded), method='PUT')
 
         if response.status == httplib.CREATED:
             # Accepted mean that container is not yet created but it will be
@@ -323,7 +327,7 @@ class CloudFilesStorageDriver(StorageDri
         raise LibcloudError('Unexpected status code: %s' % (response.status))
 
     def delete_container(self, container):
-        name = self._clean_container_name(container.name)
+        name = self._encode_container_name(container.name)
 
         # Only empty container can be deleted
         response = self.connection.request('/%s' % (name), method='DELETE')
@@ -398,8 +402,8 @@ class CloudFilesStorageDriver(StorageDri
                                 extra=extra, iterator=iterator)
 
     def delete_object(self, obj):
-        container_name = self._clean_container_name(obj.container.name)
-        object_name = self._clean_object_name(obj.name)
+        container_name = self._encode_container_name(obj.container.name)
+        object_name = self._encode_object_name(obj.name)
 
         response = self.connection.request(
             '/%s/%s' % (container_name, object_name), method='DELETE')
@@ -420,8 +424,8 @@ class CloudFilesStorageDriver(StorageDri
         completes. (optional)
         @type email: C{str}
         """
-        container_name = self._clean_container_name(obj.container.name)
-        object_name = self._clean_object_name(obj.name)
+        container_name = self._encode_container_name(obj.container.name)
+        object_name = self._encode_object_name(obj.name)
         headers = {'X-Purge-Email': email} if email else {}
 
         response = self.connection.request('/%s/%s' % (container_name,
@@ -607,14 +611,14 @@ class CloudFilesStorageDriver(StorageDri
         extra = extra or {}
         meta_data = extra.get('meta_data')
 
-        container_name_cleaned = self._clean_container_name(container.name)
-        object_name_cleaned = self._clean_object_name(object_name)
-        request_path = '/%s/%s' % (container_name_cleaned, object_name_cleaned)
+        container_name_encoded = self._encode_container_name(container.name)
+        object_name_encoded = self._encode_object_name(object_name)
+        request_path = '/%s/%s' % (container_name_encoded, object_name_encoded)
 
         headers = {'X-Auth-Token': self.connection.auth_token,
                    'X-Object-Manifest': '%s/%s/' %
-                                        (container_name_cleaned,
-                                         object_name_cleaned)}
+                                        (container_name_encoded,
+                                         object_name_encoded)}
 
         data = ''
         response = self.connection.request(request_path,
@@ -670,8 +674,8 @@ class CloudFilesStorageDriver(StorageDri
                     upload_func_kwargs, extra=None, file_path=None,
                     iterator=None, verify_hash=True):
         extra = extra or {}
-        container_name_cleaned = self._clean_container_name(container.name)
-        object_name_cleaned = self._clean_object_name(object_name)
+        container_name_encoded = self._encode_container_name(container.name)
+        object_name_encoded = self._encode_object_name(object_name)
         content_type = extra.get('content_type', None)
         meta_data = extra.get('meta_data', None)
 
@@ -681,7 +685,7 @@ class CloudFilesStorageDriver(StorageDri
                 key = 'X-Object-Meta-%s' % (key)
                 headers[key] = value
 
-        request_path = '/%s/%s' % (container_name_cleaned, object_name_cleaned)
+        request_path = '/%s/%s' % (container_name_encoded, object_name_encoded)
         result_dict = self._upload_object(
             object_name=object_name, content_type=content_type,
             upload_func=upload_func, upload_func_kwargs=upload_func_kwargs,
@@ -715,9 +719,9 @@ class CloudFilesStorageDriver(StorageDri
             raise LibcloudError('status_code=%s' % (response.status),
                                 driver=self)
 
-    def _clean_container_name(self, name):
+    def _encode_container_name(self, name):
         """
-        Clean container name.
+        Encode container name so it can be used as part of the HTTP request.
         """
         if name.startswith('/'):
             name = name[1:]
@@ -735,7 +739,7 @@ class CloudFilesStorageDriver(StorageDri
 
         return name
 
-    def _clean_object_name(self, name):
+    def _encode_object_name(self, name):
         name = urlquote(name)
         return name
 

Modified: libcloud/trunk/libcloud/test/storage/test_cloudfiles.py
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/storage/test_cloudfiles.py?rev=1485839&r1=1485838&r2=1485839&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/storage/test_cloudfiles.py (original)
+++ libcloud/trunk/libcloud/test/storage/test_cloudfiles.py Thu May 23 19:53:52 
2013
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
 # this work for additional information regarding copyright ownership.
@@ -28,6 +29,7 @@ import libcloud.utils.files
 from libcloud.utils.py3 import PY3
 from libcloud.utils.py3 import b
 from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import urlquote
 
 if PY3:
     from io import FileIO as file
@@ -669,6 +671,26 @@ class CloudFilesTests(unittest.TestCase)
         finally:
             self.driver.connection.request = _request
 
+    def test_create_container_put_object_name_encoding(self):
+        def upload_file(self, response, file_path, chunked=False,
+                     calculate_hash=True):
+            return True, 'hash343hhash89h932439jsaa89', 1000
+
+        old_func = CloudFilesStorageDriver._upload_file
+        CloudFilesStorageDriver._upload_file = upload_file
+
+        container_name = 'speci@l_name'
+        object_name = 'm@obj€ct'
+        file_path = os.path.abspath(__file__)
+
+        container = self.driver.create_container(container_name=container_name)
+        self.assertEqual(container.name, container_name)
+
+        obj = self.driver.upload_object(file_path=file_path, 
container=container,
+                                        object_name=object_name)
+        self.assertEqual(obj.name, object_name)
+        CloudFilesStorageDriver._upload_file = old_func
+
     def test_ex_enable_static_website(self):
         container = Container(name='foo_bar_container', extra={}, driver=self)
         result = self.driver.ex_enable_static_website(container=container,
@@ -884,6 +906,22 @@ class CloudFilesMockHttp(StorageMockHttp
         status_code = httplib.CREATED
         return (status_code, body, headers, httplib.responses[httplib.OK])
 
+    def _v1_MossoCloudFS_speci_40l_name(self, method, url, body, headers):
+        # test_create_container_put_object_name_encoding
+        # Verify that the name is properly url encoded
+        container_name = 'speci@l_name'
+        encoded_container_name = urlquote(container_name)
+        self.assertTrue(encoded_container_name in url)
+
+        headers = copy.deepcopy(self.base_headers)
+        body = self.fixtures.load('list_container_objects_empty.json')
+        headers = copy.deepcopy(self.base_headers)
+        headers.update({ 'content-length': 18,
+                         'date': 'Mon, 28 Feb 2011 07:52:57 GMT'
+                       })
+        status_code = httplib.CREATED
+        return (status_code, body, headers, httplib.responses[httplib.OK])
+
     def _v1_MossoCloudFS_test_create_container_ALREADY_EXISTS(
         self, method, url, body, headers):
         # test_create_container_already_exists
@@ -986,6 +1024,19 @@ class CloudFilesMockRawResponse(MockRawR
         headers['etag'] = 'hash343hhash89h932439jsaa89'
         return (httplib.CREATED, body, headers, httplib.responses[httplib.OK])
 
+    def _v1_MossoCloudFS_speci_40l_name_m_40obj_E2_82_ACct(self, method, url,
+                                                           body, headers):
+        # test_create_container_put_object_name_encoding
+        # Verify that the name is properly url encoded
+        object_name = 'm@obj€ct'
+        encoded_object_name = urlquote(object_name)
+
+        headers = copy.deepcopy(self.base_headers)
+        body = ''
+        headers['etag'] = 'hash343hhash89h932439jsaa89'
+        return (httplib.CREATED, body, headers, httplib.responses[httplib.OK])
+
+
     def _v1_MossoCloudFS_foo_bar_container_empty(self, method, url, body,
                                                  headers):
         # test_upload_object_zero_size_object


Reply via email to