summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--server/src/leap/soledad/server/_blobs.py98
-rw-r--r--server/src/leap/soledad/server/interfaces.py72
-rw-r--r--testing/tests/blobs/test_fs_backend.py2
3 files changed, 99 insertions, 73 deletions
diff --git a/server/src/leap/soledad/server/_blobs.py b/server/src/leap/soledad/server/_blobs.py
index 44abeb44..74707111 100644
--- a/server/src/leap/soledad/server/_blobs.py
+++ b/server/src/leap/soledad/server/_blobs.py
@@ -36,74 +36,28 @@ from twisted.web.client import FileBodyProducer
from twisted.web.server import NOT_DONE_YET
from twisted.internet import utils, defer
-from zope.interface import Interface, implementer
+from zope.interface import implementer
+
+from leap.common.files import mkdir_p
+from leap.soledad.server import interfaces
__all__ = ['BlobsResource']
logger = Logger()
+
# Used for sanitizers, we accept only letters, numbers, '-' and '_'
VALID_STRINGS = re.compile('^[a-zA-Z0-9_-]+$')
-# TODO some error handling needed
-# [ ] sanitize path
-
# for the future:
# [ ] isolate user avatar in a safer way
# [ ] catch timeout in the server (and delete incomplete upload)
# [ ] chunking (should we do it on the client or on the server?)
-class IBlobsBackend(Interface):
-
- """
- An interface for a BlobsBackend.
- """
-
- def read_blob(user, blob_id, request):
- """
- Read blob with a given blob_id, and write it to the passed request.
-
- :returns: a deferred that fires upon finishing.
- """
-
- def list_blobs(user, request):
- """
- Returns a json-encoded list of ids from user's blob.
-
- :returns: a deferred that fires upon finishing.
- """
-
- def tag_header(user, blob_id, request):
- """
- Adds a header 'Tag' with the last 16 bytes of the encoded file,
- which contains the tag.
-
- :returns: a deferred that fires upon finishing.
- """
-
- def write_blob(user, blob_id, request):
- """
- Write blob to the storage, reading it from the passed request.
-
- :returns: a deferred that fires upon finishing.
- """
-
- # other stuff for the API
-
- def delete_blob(user, blob_id):
- pass
-
- def get_blob_size(user, blob_id):
- pass
-
- def get_total_storage(user):
- pass
-
-
-@implementer(IBlobsBackend)
+@implementer(interfaces.IBlobsBackend)
class FilesystemBlobsBackend(object):
def __init__(self, blobs_path='/tmp/blobs/', quota=200 * 1024):
@@ -112,19 +66,6 @@ class FilesystemBlobsBackend(object):
os.makedirs(blobs_path)
self.path = blobs_path
- def list_blobs(self, user, request):
- blob_ids = []
- base_path = self._get_path(user)
- for _, _, filenames in os.walk(base_path):
- blob_ids += filenames
- return json.dumps(blob_ids)
-
- def tag_header(self, user, blob_id, request):
- with open(self._get_path(user, blob_id)) as doc_file:
- doc_file.seek(-16, 2)
- tag = base64.urlsafe_b64encode(doc_file.read())
- request.responseHeaders.setRawHeaders('Tag', [tag])
-
def read_blob(self, user, blob_id, request):
logger.info('reading blob: %s - %s' % (user, blob_id))
path = self._get_path(user, blob_id)
@@ -136,8 +77,8 @@ class FilesystemBlobsBackend(object):
def write_blob(self, user, blob_id, request):
path = self._get_path(user, blob_id)
try:
- os.makedirs(os.path.split(path)[0])
- except:
+ mkdir_p(os.path.split(path)[0])
+ except OSError:
pass
if os.path.isfile(path):
# 409 - Conflict
@@ -154,9 +95,6 @@ class FilesystemBlobsBackend(object):
fbp = FileBodyProducer(request.content)
yield fbp.startProducing(open(path, 'wb'))
- def get_total_storage(self, user):
- return self._get_disk_usage(self._get_path(user))
-
def delete_blob(self, user, blob_id):
blob_path = self._get_path(user, blob_id)
os.unlink(blob_path)
@@ -164,6 +102,22 @@ class FilesystemBlobsBackend(object):
def get_blob_size(user, blob_id):
raise NotImplementedError
+ def list_blobs(self, user, request):
+ blob_ids = []
+ base_path = self._get_path(user)
+ for _, _, filenames in os.walk(base_path):
+ blob_ids += filenames
+ return json.dumps(blob_ids)
+
+ def get_total_storage(self, user):
+ return self._get_disk_usage(self._get_path(user))
+
+ def add_tag_header(self, user, blob_id, request):
+ with open(self._get_path(user, blob_id)) as doc_file:
+ doc_file.seek(-16, 2)
+ tag = base64.urlsafe_b64encode(doc_file.read())
+ request.responseHeaders.setRawHeaders('Tag', [tag])
+
@defer.inlineCallbacks
def _get_disk_usage(self, start_path):
if not os.path.isdir(start_path):
@@ -207,7 +161,7 @@ class BlobsResource(resource.Resource):
resource.Resource.__init__(self)
self._blobs_path = blobs_path
self._handler = self.blobsFactoryClass(blobs_path)
- assert IBlobsBackend.providedBy(self._handler)
+ assert interfaces.IBlobsBackend.providedBy(self._handler)
# TODO double check credentials, we can have then
# under request.
@@ -217,7 +171,7 @@ class BlobsResource(resource.Resource):
user, blob_id = self._validate(request)
if not blob_id:
return self._handler.list_blobs(user, request)
- self._handler.tag_header(user, blob_id, request)
+ self._handler.add_tag_header(user, blob_id, request)
return self._handler.read_blob(user, blob_id, request)
def render_DELETE(self, request):
diff --git a/server/src/leap/soledad/server/interfaces.py b/server/src/leap/soledad/server/interfaces.py
new file mode 100644
index 00000000..67b04bc3
--- /dev/null
+++ b/server/src/leap/soledad/server/interfaces.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# interfaces.py
+# Copyright (C) 2017 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from zope.interface import Interface
+
+
+class IBlobsBackend(Interface):
+
+ """
+ An interface for a BlobsBackend.
+ """
+
+ def read_blob(user, blob_id, request):
+ """
+ Read blob with a given blob_id, and write it to the passed request.
+
+ :returns: a deferred that fires upon finishing.
+ """
+
+ def write_blob(user, blob_id, request):
+ """
+ Write blob to the storage, reading it from the passed request.
+
+ :returns: a deferred that fires upon finishing.
+ """
+
+ def delete_blob(user, blob_id):
+ """
+ Delete the given blob_id.
+ """
+
+ def get_blob_size(user, blob_id):
+ """
+ Get the size of the given blob id.
+ """
+
+ def list_blobs(user, request):
+ """
+ Returns a json-encoded list of ids from user's blob.
+
+ :returns: a deferred that fires upon finishing.
+ """
+
+ def get_total_storage(user):
+ """
+ Get the size used by a given user as the sum of all the blobs stored
+ unders its namespace.
+ """
+
+ def add_tag_header(user, blob_id, request):
+ """
+ Adds a header 'Tag' to the passed request object, containing the last
+ 16 bytes of the encoded blob, which according to the spec contains the
+ tag.
+
+ :returns: a deferred that fires upon finishing.
+ """
diff --git a/testing/tests/blobs/test_fs_backend.py b/testing/tests/blobs/test_fs_backend.py
index 83a5708e..27d4fc61 100644
--- a/testing/tests/blobs/test_fs_backend.py
+++ b/testing/tests/blobs/test_fs_backend.py
@@ -39,7 +39,7 @@ class FilesystemBackendTestCase(unittest.TestCase):
expected_method = Mock()
backend = _blobs.FilesystemBlobsBackend()
request = Mock(responseHeaders=Mock(setRawHeaders=expected_method))
- backend.tag_header('user', 'blob_id', request)
+ backend.add_tag_header('user', 'blob_id', request)
expected_method.assert_called_once_with('Tag', [expected_tag])