Author: tomaz
Date: Wed Jul 18 05:15:14 2012
New Revision: 1362763

URL: http://svn.apache.org/viewvc?rev=1362763&view=rev
Log:
Fix a bug with not escaping container and object name in the Atmos driver.
Contributed by Russell Keith-Magee and Benno Rice, part of LIBCLOUD-226.

Modified:
    libcloud/trunk/CHANGES
    libcloud/trunk/libcloud/storage/drivers/atmos.py
    libcloud/trunk/libcloud/test/__init__.py
    libcloud/trunk/libcloud/test/storage/test_atmos.py
    libcloud/trunk/libcloud/utils/py3.py

Modified: libcloud/trunk/CHANGES
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1362763&r1=1362762&r2=1362763&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Wed Jul 18 05:15:14 2012
@@ -125,6 +125,9 @@ Changes with Apache Libcloud in developm
       driver.download_object(). ; LIBCLOUD-227
       [Benno Rice]
 
+    - Fix a bug with not escaping container and object name in the Atmos 
driver.
+      [Russell Keith-Magee, Benno Rice]
+
 Changes with Apache Libcloud 0.10.1:
 
   *) General:

Modified: libcloud/trunk/libcloud/storage/drivers/atmos.py
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/storage/drivers/atmos.py?rev=1362763&r1=1362762&r2=1362763&view=diff
==============================================================================
--- libcloud/trunk/libcloud/storage/drivers/atmos.py (original)
+++ libcloud/trunk/libcloud/storage/drivers/atmos.py Wed Jul 18 05:15:14 2012
@@ -20,11 +20,13 @@ import hmac
 import time
 
 from libcloud.utils.py3 import PY3
+from libcloud.utils.py3 import b
 from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import next
 from libcloud.utils.py3 import urlparse
 from libcloud.utils.py3 import urlencode
-from libcloud.utils.py3 import next
-from libcloud.utils.py3 import b
+from libcloud.utils.py3 import urlquote
+from libcloud.utils.py3 import urlunquote
 
 if PY3:
     from io import FileIO as file
@@ -85,7 +87,7 @@ class AtmosConnection(ConnectionUserAndK
         return params, headers
 
     def _calculate_signature(self, params, headers):
-        pathstring = self.action
+        pathstring = urlunquote(self.action)
         if pathstring.startswith(self.driver.path):
             pathstring = pathstring[len(self.driver.path):]
         if params:
@@ -137,7 +139,7 @@ class AtmosDriver(StorageDriver):
         return containers
 
     def get_container(self, container_name):
-        path = self._namespace_path(container_name + '/?metadata/system')
+        path = self._namespace_path(container_name) + '/?metadata/system'
         try:
             result = self.connection.request(path)
         except AtmosError:
@@ -152,7 +154,7 @@ class AtmosDriver(StorageDriver):
         return Container(container_name, extra, self)
 
     def create_container(self, container_name):
-        path = self._namespace_path(container_name + '/')
+        path = self._namespace_path(container_name) + '/'
         try:
             self.connection.request(path, method='POST')
         except AtmosError:
@@ -164,7 +166,7 @@ class AtmosDriver(StorageDriver):
 
     def delete_container(self, container):
         try:
-            self.connection.request(self._namespace_path(container.name + '/'),
+            self.connection.request(self._namespace_path(container.name) + '/',
                                     method='DELETE')
         except AtmosError:
             e = sys.exc_info()[1]
@@ -176,8 +178,8 @@ class AtmosDriver(StorageDriver):
 
     def get_object(self, container_name, object_name):
         container = self.get_container(container_name)
-        path = container_name + '/' + object_name
-        path = self._namespace_path(path)
+        object_name_cleaned = self._clean_object_name(object_name)
+        path = self._namespace_path(container_name) + '/' + object_name_cleaned
 
         try:
             result = self.connection.request(path + '?metadata/system')
@@ -206,12 +208,12 @@ class AtmosDriver(StorageDriver):
     def upload_object(self, file_path, container, object_name, extra=None,
                       verify_hash=True):
         upload_func = self._upload_file
-        upload_func_kwargs = { 'file_path': file_path }
+        upload_func_kwargs = {'file_path': file_path}
         method = 'PUT'
 
         extra = extra or {}
-        request_path = container.name + '/' + object_name
-        request_path = self._namespace_path(request_path)
+        object_name_cleaned = self._clean_object_name(object_name)
+        request_path = self._namespace_path(container.name) + '/' + 
object_name_cleaned
         content_type = extra.get('content_type', None)
 
         try:
@@ -338,7 +340,7 @@ class AtmosDriver(StorageDriver):
                                 success_status_code=httplib.OK)
 
     def delete_object(self, obj):
-        path = self._namespace_path(obj.container.name + '/' + obj.name)
+        path = self._namespace_path(obj.container.name) + '/' + 
self._clean_object_name(obj.name)
         try:
             self.connection.request(path, method='DELETE')
         except AtmosError:
@@ -398,8 +400,12 @@ class AtmosDriver(StorageDriver):
             })
         return entries
 
+    def _clean_object_name(self, name):
+        name = urlquote(name)
+        return name
+
     def _namespace_path(self, path):
-        return self.path + '/rest/namespace/' + path
+        return self.path + '/rest/namespace/' + urlquote(path)
 
     def _object_path(self, object_id):
         return self.path + '/rest/objects/' + object_id
@@ -418,7 +424,7 @@ class AtmosDriver(StorageDriver):
     def _get_more(self, last_key, value_dict):
         container = value_dict['container']
         headers = {'x-emc-include-meta': '1'}
-        path = self._namespace_path(container.name + '/')
+        path = self._namespace_path(container.name) + '/'
         result = self.connection.request(path, headers=headers)
         entries = self._list_objects(result.object, object_type='regular')
         objects = []

Modified: libcloud/trunk/libcloud/test/__init__.py
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/__init__.py?rev=1362763&r1=1362762&r2=1362763&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/__init__.py (original)
+++ libcloud/trunk/libcloud/test/__init__.py Wed Jul 18 05:15:14 2012
@@ -158,7 +158,7 @@ class MockHttp(BaseMockHttpObject):
         meth_name = self._get_method_name(type=self.type,
                                           use_param=self.use_param,
                                           qs=qs, path=path)
-        meth = getattr(self, meth_name)
+        meth = getattr(self, meth_name.replace('%', '_'))
 
         if self.test and isinstance(self.test, LibcloudTestCase):
             self.test._add_visited_url(url=url)
@@ -281,7 +281,7 @@ class MockRawResponse(BaseMockHttpObject
             meth_name = self._get_method_name(type=self.type,
                                               use_param=False, qs=None,
                                               path=self.connection.action)
-            meth = getattr(self, meth_name)
+            meth = getattr(self, meth_name.replace('%', '_'))
             result = meth(self.connection.method, None, None, None)
             self._status, self._body, self._headers, self._reason = result
             self._response = self.responseCls(self._status, self._body,

Modified: libcloud/trunk/libcloud/test/storage/test_atmos.py
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/storage/test_atmos.py?rev=1362763&r1=1362762&r2=1362763&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/storage/test_atmos.py (original)
+++ libcloud/trunk/libcloud/test/storage/test_atmos.py Wed Jul 18 05:15:14 2012
@@ -90,6 +90,12 @@ class AtmosTests(unittest.TestCase):
         self.assertEqual(container.extra['object_id'],
                          'b21cb59a2ba339d1afdd4810010b0a5aba2ab6b9')
 
+    def test_get_container_escaped(self):
+        container = self.driver.get_container(container_name='test & 
container')
+        self.assertEqual(container.name, 'test & container')
+        self.assertEqual(container.extra['object_id'],
+                         'b21cb59a2ba339d1afdd4810010b0a5aba2ab6b9')
+
     def test_get_container_not_found(self):
         try:
             self.driver.get_container(container_name='not_found')
@@ -157,6 +163,18 @@ class AtmosTests(unittest.TestCase):
         self.assertEqual(obj.meta_data['foo-bar'], 'test 1')
         self.assertEqual(obj.meta_data['bar-foo'], 'test 2')
 
+    def test_get_object_escaped(self):
+        obj = self.driver.get_object(container_name='test & container',
+                                     object_name='test & object')
+        self.assertEqual(obj.container.name, 'test & container')
+        self.assertEqual(obj.size, 555)
+        self.assertEqual(obj.hash, '6b21c4a111ac178feacf9ec9d0c71f17')
+        self.assertEqual(obj.extra['object_id'],
+                         '322dce3763aadc41acc55ef47867b8d74e45c31d6643')
+        self.assertEqual(
+            obj.extra['last_modified'], 'Tue, 25 Jan 2011 22:01:49 GMT')
+        self.assertEqual(obj.meta_data['foo-bar'], 'test 1')
+        self.assertEqual(obj.meta_data['bar-foo'], 'test 2')
 
     def test_get_object_not_found(self):
         try:
@@ -176,6 +194,15 @@ class AtmosTests(unittest.TestCase):
         status = self.driver.delete_object(obj=obj)
         self.assertTrue(status)
 
+    def test_delete_object_escaped_success(self):
+        container = Container(name='foo & bar_container', extra={},
+                              driver=self.driver)
+        obj = Object(name='foo & bar_object', size=1000, hash=None, extra={},
+                     container=container, meta_data=None,
+                     driver=self.driver)
+        status = self.driver.delete_object(obj=obj)
+        self.assertTrue(status)
+
     def test_delete_object_not_found(self):
         AtmosMockHttp.type = 'NOT_FOUND'
         container = Container(name='foo_bar_container', extra={},
@@ -203,6 +230,19 @@ class AtmosTests(unittest.TestCase):
                                              delete_on_failure=True)
         self.assertTrue(result)
 
+    def test_download_object_escaped_success(self):
+        container = Container(name='foo & bar_container', extra={},
+                              driver=self.driver)
+        obj = Object(name='foo & bar_object', size=1000, hash=None, extra={},
+                     container=container, meta_data=None,
+                     driver=self.driver)
+        destination_path = os.path.abspath(__file__) + '.temp'
+        result = self.driver.download_object(obj=obj,
+                                             destination_path=destination_path,
+                                             overwrite_existing=False,
+                                             delete_on_failure=True)
+        self.assertTrue(result)
+
     def test_download_object_success_not_found(self):
         AtmosMockRawResponse.type = 'NOT_FOUND'
         container = Container(name='foo_bar_container', extra={},
@@ -234,6 +274,16 @@ class AtmosTests(unittest.TestCase):
         stream = self.driver.download_object_as_stream(obj=obj, 
chunk_size=None)
         self.assertTrue(hasattr(stream, '__iter__'))
 
+    def test_download_object_as_stream_escaped(self):
+        container = Container(name='foo & bar_container', extra={},
+                              driver=self.driver)
+        obj = Object(name='foo & bar_object', size=1000, hash=None, extra={},
+                     container=container, meta_data=None,
+                     driver=self.driver)
+
+        stream = self.driver.download_object_as_stream(obj=obj, 
chunk_size=None)
+        self.assertTrue(hasattr(stream, '__iter__'))
+
     def test_upload_object_success(self):
         def upload_file(self, response, file_path, chunked=False,
                      calculate_hash=True):
@@ -244,7 +294,7 @@ class AtmosTests(unittest.TestCase):
         path = os.path.abspath(__file__)
         container = Container(name='fbc', extra={}, driver=self)
         object_name = 'ftu'
-        extra = {'meta_data': { 'some-value': 'foobar'}}
+        extra = {'meta_data': {'some-value': 'foobar'}}
         obj = self.driver.upload_object(file_path=path, container=container,
                                         extra=extra, object_name=object_name)
         self.assertEqual(obj.name, 'ftu')
@@ -347,6 +397,8 @@ class AtmosTests(unittest.TestCase):
         test_values = [
             ('GET', '/rest/namespace/foo', '', {},
                 'WfSASIA25TuqO2n0aO9k/dtg6S0='),
+            ('GET', '/rest/namespace/foo%20%26%20bar', '', {},
+                'vmlqXqcInxxoP4YX5mR09BonjX4='),
             ('POST', '/rest/namespace/foo', '', {},
                 'oYKdsF+1DOuUT7iX5CJCDym2EQk='),
             ('PUT', '/rest/namespace/foo', '', {},
@@ -422,6 +474,13 @@ class AtmosMockHttp(StorageMockHttp, uni
         }
         return (httplib.OK, '', headers, httplib.responses[httplib.OK])
 
+    def _rest_namespace_test_20_26_20container__metadata_system(self, method, 
url, body,
+                                                                headers):
+        headers = {
+            'x-emc-meta': 'objectid=b21cb59a2ba339d1afdd4810010b0a5aba2ab6b9'
+        }
+        return (httplib.OK, '', headers, httplib.responses[httplib.OK])
+
     def _rest_namespace_not_found__metadata_system(self, method, url, body,
                                                    headers):
         body = self.fixtures.load('not_found.xml')
@@ -473,6 +532,19 @@ class AtmosMockHttp(StorageMockHttp, uni
         }
         return (httplib.OK, '', headers, httplib.responses[httplib.OK])
 
+    def 
_rest_namespace_test_20_26_20container_test_20_26_20object_metadata_system(self,
 method,
+                                                                               
     url, body,
+                                                                               
     headers):
+        meta = {
+            'objectid': '322dce3763aadc41acc55ef47867b8d74e45c31d6643',
+            'size': '555',
+            'mtime': '2011-01-25T22:01:49Z'
+        }
+        headers = {
+            'x-emc-meta': ', '.join([k + '=' + v for k, v in 
list(meta.items())])
+        }
+        return (httplib.OK, '', headers, httplib.responses[httplib.OK])
+
     def _rest_namespace_test_container_test_object_metadata_user(self, method,
                                                                  url, body,
                                                                  headers):
@@ -486,6 +558,19 @@ class AtmosMockHttp(StorageMockHttp, uni
         }
         return (httplib.OK, '', headers, httplib.responses[httplib.OK])
 
+    def 
_rest_namespace_test_20_26_20container_test_20_26_20object_metadata_user(self, 
method,
+                                                                               
  url, body,
+                                                                               
  headers):
+        meta = {
+            'md5': '6b21c4a111ac178feacf9ec9d0c71f17',
+            'foo-bar': 'test 1',
+            'bar-foo': 'test 2',
+        }
+        headers = {
+            'x-emc-meta': ', '.join([k + '=' + v for k, v in 
list(meta.items())])
+        }
+        return (httplib.OK, '', headers, httplib.responses[httplib.OK])
+
     def _rest_namespace_test_container_not_found_metadata_system(self, method,
                                                                  url, body,
                                                                  headers):
@@ -497,6 +582,10 @@ class AtmosMockHttp(StorageMockHttp, uni
                                                          body, headers):
         return (httplib.OK, '', {}, httplib.responses[httplib.OK])
 
+    def _rest_namespace_foo_20_26_20bar_container_foo_20_26_20bar_object(self, 
method, url,
+                                                                         body, 
headers):
+        return (httplib.OK, '', {}, httplib.responses[httplib.OK])
+
     def _rest_namespace_foo_bar_container_foo_bar_object_NOT_FOUND(self, 
method,
                                                                    url, body,
                                                                    headers):
@@ -557,6 +646,12 @@ class AtmosMockRawResponse(MockRawRespon
         self._data = self._generate_random_data(1000)
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _rest_namespace_foo_20_26_20bar_container_foo_20_26_20bar_object(self, 
method, url,
+                                                                         body, 
headers):
+        body = 'test'
+        self._data = self._generate_random_data(1000)
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
     def _rest_namespace_foo_bar_container_foo_bar_object_NOT_FOUND(self, 
method,
                                                                    url, body,
                                                                    headers):

Modified: libcloud/trunk/libcloud/utils/py3.py
URL: 
http://svn.apache.org/viewvc/libcloud/trunk/libcloud/utils/py3.py?rev=1362763&r1=1362762&r2=1362763&view=diff
==============================================================================
--- libcloud/trunk/libcloud/utils/py3.py (original)
+++ libcloud/trunk/libcloud/utils/py3.py Wed Jul 18 05:15:14 2012
@@ -36,6 +36,7 @@ if sys.version_info >= (3, 0):
     import urllib.parse as urlparse
     import xmlrpc.client as xmlrpclib
     from urllib.parse import quote as urlquote
+    from urllib.parse import unquote as urlunquote
     from urllib.parse import urlencode as urlencode
 
     basestring = str
@@ -67,6 +68,7 @@ else:
     import urlparse
     import xmlrpclib
     from urllib import quote as urlquote
+    from urllib import unquote as urlunquote
     from urllib import urlencode as urlencode
 
     basestring = unicode = str


Reply via email to