========================= ================================
0.7 (Sept 28, 2011) Suggest upload/download methods using hashmaps.
\ Propose syncing algorithm.
+\ Support cross-account object copy and move.
0.6 (Sept 13, 2011) Reply with Merkle hash as the ETag when updating objects.
\ Include version id in object replace/change replies.
\ Change conflict (409) replies format to text.
Transfer-Encoding Set to ``chunked`` to specify incremental uploading (if used, ``Content-Length`` is ignored)
X-Copy-From The source path in the form ``/<container>/<object>``
X-Move-From The source path in the form ``/<container>/<object>``
+X-Source-Account The source account to copy/move from
X-Source-Version The source version to copy from
Content-Encoding The encoding of the object (optional)
Content-Disposition The presentation style of the object (optional)
If-Match Proceed if ETags match with object
If-None-Match Proceed if ETags don't match with object
Destination The destination path in the form ``/<container>/<object>``
+Destination-Account The destination account to copy to
Content-Type The MIME content type of the object (optional)
Content-Encoding The encoding of the object (optional)
Content-Disposition The presentation style of the object (optional)
* Object versions - parameter ``version`` in ``HEAD``/``GET`` (list versions with ``GET``), ``X-Object-Version-*`` meta in replies, ``X-Source-Version`` in ``PUT``/``COPY``.
* Sharing/publishing with ``X-Object-Sharing``, ``X-Object-Public`` at the object level. Cross-user operations are allowed - controlled by sharing directives. Available actions in cross-user requests are reported with ``X-Object-Allowed-To``. Permissions may include groups defined with ``X-Account-Group-*`` at the account level. These apply to the object - not its versions.
* Support for prefix-based inheritance when enforcing permissions. Parent object carrying the authorization directives is reported in ``X-Object-Shared-By``.
+* Copy and move between accounts with ``X-Source-Account`` and ``Destination-Account`` headers.
* Large object support with ``X-Object-Manifest``.
* Trace the user that created/modified an object with ``X-Object-Modified-By``.
* Purge container/object history with the ``until`` parameter in ``DELETE``.
if copy_from or move_from:
content_length = get_content_length(request) # Required by the API.
+ src_account = smart_unicode(request.META.get('HTTP_X_SOURCE_ACCOUNT'), strings_only=True)
+ if not src_account:
+ src_account = request.user
if move_from:
try:
src_container, src_name = split_container_object_string(move_from)
except ValueError:
raise BadRequest('Invalid X-Move-From header')
- version_id = copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=True)
+ version_id = copy_or_move_object(request, src_account, src_container, src_name,
+ v_account, v_container, v_object, move=True)
else:
try:
src_container, src_name = split_container_object_string(copy_from)
except ValueError:
raise BadRequest('Invalid X-Copy-From header')
- version_id = copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=False)
+ version_id = copy_or_move_object(request, src_account, src_container, src_name,
+ v_account, v_container, v_object, move=False)
response = HttpResponse(status=201)
response['X-Object-Version'] = version_id
return response
# unauthorized (401),
# badRequest (400)
- dest_path = request.META.get('HTTP_DESTINATION')
+ dest_account = smart_unicode(request.META.get('HTTP_DESTINATION_ACCOUNT'), strings_only=True)
+ if not dest_account:
+ dest_account = request.user
+ dest_path = smart_unicode(request.META.get('HTTP_DESTINATION'), strings_only=True)
if not dest_path:
raise BadRequest('Missing Destination header')
try:
raise ItemNotFound('Container or object does not exist')
validate_matching_preconditions(request, meta)
- version_id = copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=False)
+ version_id = copy_or_move_object(request, v_account, v_container, v_object,
+ dest_account, dest_container, dest_name, move=False)
response = HttpResponse(status=201)
response['X-Object-Version'] = version_id
return response
# unauthorized (401),
# badRequest (400)
- dest_path = request.META.get('HTTP_DESTINATION')
+ dest_account = smart_unicode(request.META.get('HTTP_DESTINATION_ACCOUNT'), strings_only=True)
+ if not dest_account:
+ dest_account = request.user
+ dest_path = smart_unicode(request.META.get('HTTP_DESTINATION'), strings_only=True)
if not dest_path:
raise BadRequest('Missing Destination header')
try:
raise ItemNotFound('Container or object does not exist')
validate_matching_preconditions(request, meta)
- version_id = copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=True)
+ version_id = copy_or_move_object(request, v_account, v_container, v_object,
+ dest_account, dest_container, dest_name, move=True)
response = HttpResponse(status=201)
response['X-Object-Version'] = version_id
return response
raise ValueError
return s[:pos], s[(pos + 1):]
-def copy_or_move_object(request, v_account, src_container, src_name, dest_container, dest_name, move=False):
+def copy_or_move_object(request, src_account, src_container, src_name, dest_account, dest_container, dest_name, move=False):
"""Copy or move an object."""
meta, permissions, public = get_object_headers(request)
- src_version = request.META.get('HTTP_X_SOURCE_VERSION')
+ print '---', meta, permissions, public
+ src_version = request.META.get('HTTP_X_SOURCE_VERSION')
try:
if move:
- version_id = request.backend.move_object(request.user, v_account,
- src_container, src_name, dest_container, dest_name,
- meta, False, permissions)
+ version_id = request.backend.move_object(request.user, src_account, src_container, src_name,
+ dest_account, dest_container, dest_name,
+ meta, False, permissions)
else:
- version_id = request.backend.copy_object(request.user, v_account,
- src_container, src_name, dest_container, dest_name,
- meta, False, permissions, src_version)
+ version_id = request.backend.copy_object(request.user, src_account, src_container, src_name,
+ dest_account, dest_container, dest_name,
+ meta, False, permissions, src_version)
except NotAllowedError:
raise Unauthorized('Access denied')
except (NameError, IndexError):
raise Conflict(json.dumps(e.data))
if public is not None:
try:
- request.backend.update_object_public(request.user, v_account,
- dest_container, dest_name, public)
+ request.backend.update_object_public(request.user, dest_account, dest_container, dest_name, public)
except NotAllowedError:
raise Unauthorized('Access denied')
except NameError:
"""
return ''
- def copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
+ def copy_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
"""Copy an object's data and metadata and return the new version.
Parameters:
"""
return ''
- def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
+ def move_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
"""Move an object's data and metadata and return the new version.
Parameters:
self.permissions.access_set(path, permissions)
return dest_version_id
- def _copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
- if permissions is not None and user != account:
+ def _copy_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
+ if permissions is not None and user != dest_account:
raise NotAllowedError
- self._can_read(user, account, src_container, src_name)
- self._can_write(user, account, dest_container, dest_name)
- src_path, src_node = self._lookup_object(account, src_container, src_name)
+ self._can_read(user, src_account, src_container, src_name)
+ self._can_write(user, dest_account, dest_container, dest_name)
+ src_path, src_node = self._lookup_object(src_account, src_container, src_name)
self._get_version(src_node, src_version)
if permissions is not None:
- dest_path = '/'.join((account, container, name))
+ dest_path = '/'.join((dest_account, dest_container, dest_name))
self._check_permissions(dest_path, permissions)
- dest_path, dest_node = self._put_object_node(account, dest_container, dest_name)
+ dest_path, dest_node = self._put_object_node(dest_account, dest_container, dest_name)
src_version_id, dest_version_id = self._copy_version(user, src_node, src_version, dest_node)
if src_version_id is not None:
self._copy_data(src_version_id, dest_version_id)
return dest_version_id
@backend_method
- def copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
+ def copy_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
"""Copy an object's data and metadata."""
- logger.debug("copy_object: %s %s %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, src_version)
- return self._copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, src_version)
+ logger.debug("copy_object: %s %s %s %s %s %s %s %s %s %s", src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta, replace_meta, permissions, src_version)
+ return self._copy_object(user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta, replace_meta, permissions, src_version)
@backend_method
- def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
+ def move_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
"""Move an object's data and metadata."""
- logger.debug("move_object: %s %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions)
- dest_version_id = self._copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
- self._delete_object(user, account, src_container, src_name)
+ logger.debug("move_object: %s %s %s %s %s %s %s %s %s", src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta, replace_meta, permissions)
+ if user != src_account:
+ raise NotAllowedError
+ dest_version_id = self._copy_object(user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
+ self._delete_object(user, src_account, src_container, src_name)
return dest_version_id
def _delete_object(self, user, account, container, name, until=None):
return dest_version_id
@backend_method
- def copy_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
+ def copy_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None, src_version=None):
"""Copy an object's data and metadata."""
- logger.debug("copy_object: %s %s %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, src_version)
- if permissions is not None and user != account:
+ logger.debug("copy_object: %s %s %s %s %s %s %s %s %s %s", src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta, replace_meta, permissions, src_version)
+ if permissions is not None and user != dest_account:
raise NotAllowedError
- self._can_read(user, account, src_container, src_name)
- self._can_write(user, account, dest_container, dest_name)
- self._get_containerinfo(account, src_container)
+ self._can_read(user, src_account, src_container, src_name)
+ self._can_write(user, dest_account, dest_container, dest_name)
+ self._get_containerinfo(src_account, src_container)
if src_version is None:
- src_path = self._get_objectinfo(account, src_container, src_name)[0]
+ src_path = self._get_objectinfo(src_account, src_container, src_name)[0]
else:
- src_path = '/'.join((account, src_container, src_name))
- dest_path = self._get_containerinfo(account, dest_container)[0]
+ src_path = '/'.join((src_account, src_container, src_name))
+ dest_path = self._get_containerinfo(dest_account, dest_container)[0]
dest_path = '/'.join((dest_path, dest_name))
if permissions is not None:
r, w = self._check_permissions(dest_path, permissions)
return dest_version_id
@backend_method
- def move_object(self, user, account, src_container, src_name, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
+ def move_object(self, user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta={}, replace_meta=False, permissions=None):
"""Move an object's data and metadata."""
- logger.debug("move_object: %s %s %s %s %s %s %s %s", account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions)
- dest_version_id = self.copy_object(user, account, src_container, src_name, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
- self.delete_object(user, account, src_container, src_name)
+ logger.debug("move_object: %s %s %s %s %s %s %s %s %s", src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta, replace_meta, permissions)
+ dest_version_id = self.copy_object(user, src_account, src_container, src_name, dest_account, dest_container, dest_name, dest_meta, replace_meta, permissions, None)
+ self.delete_object(user, src_account, src_container, src_name)
return dest_version_id
@backend_method