1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
38 from django.conf import settings
39 from django.http import HttpResponse
40 from django.template.loader import render_to_string
41 from django.utils import simplejson as json
42 from django.utils.http import parse_etags
44 from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, ItemNotFound, Conflict,
45 LengthRequired, PreconditionFailed, RangeNotSatisfiable, UnprocessableEntity)
46 from pithos.api.util import (format_header_key, printable_header_dict, get_account_headers,
47 put_account_headers, get_container_headers, put_container_headers, get_object_headers, put_object_headers,
48 update_manifest_meta, update_sharing_meta, update_public_meta, validate_modification_preconditions,
49 validate_matching_preconditions, split_container_object_string, copy_or_move_object,
50 get_int_parameter, get_content_length, get_content_range, raw_input_socket,
51 socket_read_iterator, object_data_response, put_object_block, hashmap_hash, api_method)
52 from pithos.backends import backend
53 from pithos.backends.base import NotAllowedError
56 logger = logging.getLogger(__name__)
59 def top_demux(request):
60 if request.method == 'GET':
61 return authenticate(request)
63 return method_not_allowed(request)
65 def account_demux(request, v_account):
66 if request.method == 'HEAD':
67 return account_meta(request, v_account)
68 elif request.method == 'POST':
69 return account_update(request, v_account)
70 elif request.method == 'GET':
71 return container_list(request, v_account)
73 return method_not_allowed(request)
75 def container_demux(request, v_account, v_container):
76 if request.method == 'HEAD':
77 return container_meta(request, v_account, v_container)
78 elif request.method == 'PUT':
79 return container_create(request, v_account, v_container)
80 elif request.method == 'POST':
81 return container_update(request, v_account, v_container)
82 elif request.method == 'DELETE':
83 return container_delete(request, v_account, v_container)
84 elif request.method == 'GET':
85 return object_list(request, v_account, v_container)
87 return method_not_allowed(request)
89 def object_demux(request, v_account, v_container, v_object):
90 if request.method == 'HEAD':
91 return object_meta(request, v_account, v_container, v_object)
92 elif request.method == 'GET':
93 return object_read(request, v_account, v_container, v_object)
94 elif request.method == 'PUT':
95 return object_write(request, v_account, v_container, v_object)
96 elif request.method == 'COPY':
97 return object_copy(request, v_account, v_container, v_object)
98 elif request.method == 'MOVE':
99 return object_move(request, v_account, v_container, v_object)
100 elif request.method == 'POST':
101 return object_update(request, v_account, v_container, v_object)
102 elif request.method == 'DELETE':
103 return object_delete(request, v_account, v_container, v_object)
105 return method_not_allowed(request)
108 def authenticate(request):
109 # Normal Response Codes: 204
110 # Error Response Codes: serviceUnavailable (503),
111 # unauthorized (401),
114 x_auth_user = request.META.get('HTTP_X_AUTH_USER')
115 x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
116 if not x_auth_user or not x_auth_key:
117 raise BadRequest('Missing X-Auth-User or X-Auth-Key header')
118 response = HttpResponse(status=204)
119 inv_auth_tokens = dict((v, k) for k, v in settings.AUTH_TOKENS.items())
120 response['X-Auth-Token'] = inv_auth_tokens.get(x_auth_user, '0000')
121 response['X-Storage-Url'] = os.path.join(request.build_absolute_uri(), 'demo')
125 def account_meta(request, v_account):
126 # Normal Response Codes: 204
127 # Error Response Codes: serviceUnavailable (503),
128 # unauthorized (401),
131 until = get_int_parameter(request, 'until')
133 meta = backend.get_account_meta(request.user, v_account, until)
134 groups = backend.get_account_groups(request.user, v_account)
135 except NotAllowedError:
136 raise Unauthorized('Access denied')
138 response = HttpResponse(status=204)
139 put_account_headers(response, meta, groups)
143 def account_update(request, v_account):
144 # Normal Response Codes: 202
145 # Error Response Codes: serviceUnavailable (503),
146 # unauthorized (401),
149 meta, groups = get_account_headers(request)
151 if 'update' in request.GET:
155 backend.update_account_groups(request.user, v_account, groups, replace)
156 except NotAllowedError:
157 raise Unauthorized('Access denied')
159 raise BadRequest('Invalid groups header')
161 backend.update_account_meta(request.user, v_account, meta, replace)
162 except NotAllowedError:
163 raise Unauthorized('Access denied')
164 return HttpResponse(status=202)
166 @api_method('GET', format_allowed=True)
167 def container_list(request, v_account):
168 # Normal Response Codes: 200, 204
169 # Error Response Codes: serviceUnavailable (503),
170 # itemNotFound (404),
171 # unauthorized (401),
174 until = get_int_parameter(request, 'until')
176 meta = backend.get_account_meta(request.user, v_account, until)
177 groups = backend.get_account_groups(request.user, v_account)
178 except NotAllowedError:
179 raise Unauthorized('Access denied')
181 validate_modification_preconditions(request, meta)
183 response = HttpResponse()
184 put_account_headers(response, meta, groups)
186 marker = request.GET.get('marker')
187 limit = request.GET.get('limit')
197 containers = backend.list_containers(request.user, v_account, marker, limit, until)
198 except NotAllowedError:
199 raise Unauthorized('Access denied')
203 if request.serialization == 'text':
204 if len(containers) == 0:
205 # The cloudfiles python bindings expect 200 if json/xml.
206 response.status_code = 204
208 response.status_code = 200
209 response.content = '\n'.join([x[0] for x in containers]) + '\n'
216 meta = backend.get_container_meta(request.user, v_account, x[0], until)
217 policy = backend.get_container_policy(request.user, v_account, x[0])
218 for k, v in policy.iteritems():
219 meta['X-Container-Policy-' + k] = v
220 container_meta.append(printable_header_dict(meta))
221 except NotAllowedError:
222 raise Unauthorized('Access denied')
225 if request.serialization == 'xml':
226 data = render_to_string('containers.xml', {'account': v_account, 'containers': container_meta})
227 elif request.serialization == 'json':
228 data = json.dumps(container_meta)
229 response.status_code = 200
230 response.content = data
234 def container_meta(request, v_account, v_container):
235 # Normal Response Codes: 204
236 # Error Response Codes: serviceUnavailable (503),
237 # itemNotFound (404),
238 # unauthorized (401),
241 until = get_int_parameter(request, 'until')
243 meta = backend.get_container_meta(request.user, v_account, v_container, until)
244 meta['object_meta'] = backend.list_object_meta(request.user, v_account, v_container, until)
245 policy = backend.get_container_policy(request.user, v_account, v_container)
246 except NotAllowedError:
247 raise Unauthorized('Access denied')
249 raise ItemNotFound('Container does not exist')
251 response = HttpResponse(status=204)
252 put_container_headers(response, meta, policy)
256 def container_create(request, v_account, v_container):
257 # Normal Response Codes: 201, 202
258 # Error Response Codes: serviceUnavailable (503),
259 # itemNotFound (404),
260 # unauthorized (401),
263 meta, policy = get_container_headers(request)
266 backend.put_container(request.user, v_account, v_container, policy)
268 except NotAllowedError:
269 raise Unauthorized('Access denied')
275 backend.update_container_meta(request.user, v_account, v_container, meta, replace=True)
276 except NotAllowedError:
277 raise Unauthorized('Access denied')
279 raise ItemNotFound('Container does not exist')
281 return HttpResponse(status=ret)
284 def container_update(request, v_account, v_container):
285 # Normal Response Codes: 202
286 # Error Response Codes: serviceUnavailable (503),
287 # itemNotFound (404),
288 # unauthorized (401),
291 meta, policy = get_container_headers(request)
293 if 'update' in request.GET:
297 backend.update_container_policy(request.user, v_account, v_container, policy, replace)
298 except NotAllowedError:
299 raise Unauthorized('Access denied')
301 raise ItemNotFound('Container does not exist')
303 raise BadRequest('Invalid policy header')
305 backend.update_container_meta(request.user, v_account, v_container, meta, replace)
306 except NotAllowedError:
307 raise Unauthorized('Access denied')
309 raise ItemNotFound('Container does not exist')
310 return HttpResponse(status=202)
312 @api_method('DELETE')
313 def container_delete(request, v_account, v_container):
314 # Normal Response Codes: 204
315 # Error Response Codes: serviceUnavailable (503),
317 # itemNotFound (404),
318 # unauthorized (401),
322 backend.delete_container(request.user, v_account, v_container)
323 except NotAllowedError:
324 raise Unauthorized('Access denied')
326 raise ItemNotFound('Container does not exist')
328 raise Conflict('Container is not empty')
329 return HttpResponse(status=204)
331 @api_method('GET', format_allowed=True)
332 def object_list(request, v_account, v_container):
333 # Normal Response Codes: 200, 204
334 # Error Response Codes: serviceUnavailable (503),
335 # itemNotFound (404),
336 # unauthorized (401),
339 until = get_int_parameter(request, 'until')
341 meta = backend.get_container_meta(request.user, v_account, v_container, until)
342 meta['object_meta'] = backend.list_object_meta(request.user, v_account, v_container, until)
343 policy = backend.get_container_policy(request.user, v_account, v_container)
344 except NotAllowedError:
345 raise Unauthorized('Access denied')
347 raise ItemNotFound('Container does not exist')
349 validate_modification_preconditions(request, meta)
351 response = HttpResponse()
352 put_container_headers(response, meta, policy)
354 path = request.GET.get('path')
355 prefix = request.GET.get('prefix')
356 delimiter = request.GET.get('delimiter')
358 # Path overrides prefix and delimiter.
366 if prefix and delimiter:
367 prefix = prefix + delimiter
370 prefix = prefix.lstrip('/')
372 marker = request.GET.get('marker')
373 limit = request.GET.get('limit')
382 keys = request.GET.get('meta')
384 keys = keys.split(',')
385 keys = [format_header_key('X-Object-Meta-' + x.strip()) for x in keys if x.strip() != '']
390 objects = backend.list_objects(request.user, v_account, v_container, prefix, delimiter, marker, limit, virtual, keys, until)
391 except NotAllowedError:
392 raise Unauthorized('Access denied')
394 raise ItemNotFound('Container does not exist')
396 if request.serialization == 'text':
397 if len(objects) == 0:
398 # The cloudfiles python bindings expect 200 if json/xml.
399 response.status_code = 204
401 response.status_code = 200
402 response.content = '\n'.join([x[0] for x in objects]) + '\n'
408 # Virtual objects/directories.
409 object_meta.append({'subdir': x[0]})
412 meta = backend.get_object_meta(request.user, v_account, v_container, x[0], x[1])
414 permissions = backend.get_object_permissions(request.user, v_account, v_container, x[0])
415 public = backend.get_object_public(request.user, v_account, v_container, x[0])
419 except NotAllowedError:
420 raise Unauthorized('Access denied')
423 update_sharing_meta(permissions, v_account, v_container, x[0], meta)
424 update_public_meta(public, meta)
425 object_meta.append(printable_header_dict(meta))
426 if request.serialization == 'xml':
427 data = render_to_string('objects.xml', {'container': v_container, 'objects': object_meta})
428 elif request.serialization == 'json':
429 data = json.dumps(object_meta)
430 response.status_code = 200
431 response.content = data
435 def object_meta(request, v_account, v_container, v_object):
436 # Normal Response Codes: 204
437 # Error Response Codes: serviceUnavailable (503),
438 # itemNotFound (404),
439 # unauthorized (401),
442 version = request.GET.get('version')
444 meta = backend.get_object_meta(request.user, v_account, v_container, v_object, version)
446 permissions = backend.get_object_permissions(request.user, v_account, v_container, v_object)
447 public = backend.get_object_public(request.user, v_account, v_container, v_object)
451 except NotAllowedError:
452 raise Unauthorized('Access denied')
454 raise ItemNotFound('Object does not exist')
456 raise ItemNotFound('Version does not exist')
458 update_manifest_meta(request, v_account, meta)
459 update_sharing_meta(permissions, v_account, v_container, v_object, meta)
460 update_public_meta(public, meta)
462 response = HttpResponse(status=200)
463 put_object_headers(response, meta)
466 @api_method('GET', format_allowed=True)
467 def object_read(request, v_account, v_container, v_object):
468 # Normal Response Codes: 200, 206
469 # Error Response Codes: serviceUnavailable (503),
470 # rangeNotSatisfiable (416),
471 # preconditionFailed (412),
472 # itemNotFound (404),
473 # unauthorized (401),
477 version = request.GET.get('version')
479 # Reply with the version list. Do this first, as the object may be deleted.
480 if version == 'list':
481 if request.serialization == 'text':
482 raise BadRequest('No format specified for version list.')
485 v = backend.list_versions(request.user, v_account, v_container, v_object)
486 except NotAllowedError:
487 raise Unauthorized('Access denied')
489 if request.serialization == 'xml':
490 d['object'] = v_object
491 data = render_to_string('versions.xml', d)
492 elif request.serialization == 'json':
495 response = HttpResponse(data, status=200)
496 response['Content-Length'] = len(data)
500 meta = backend.get_object_meta(request.user, v_account, v_container, v_object, version)
502 permissions = backend.get_object_permissions(request.user, v_account, v_container, v_object)
503 public = backend.update_object_public(request.user, v_account, v_container, v_object)
507 except NotAllowedError:
508 raise Unauthorized('Access denied')
510 raise ItemNotFound('Object does not exist')
512 raise ItemNotFound('Version does not exist')
514 update_manifest_meta(request, v_account, meta)
515 update_sharing_meta(permissions, v_account, v_container, v_object, meta)
516 update_public_meta(public, meta)
518 # Evaluate conditions.
519 validate_modification_preconditions(request, meta)
521 validate_matching_preconditions(request, meta)
523 response = HttpResponse(status=304)
524 response['ETag'] = meta['hash']
529 if 'X-Object-Manifest' in meta:
531 src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
532 objects = backend.list_objects(request.user, v_account, src_container, prefix=src_name, virtual=False)
533 except NotAllowedError:
534 raise Unauthorized('Access denied')
536 raise BadRequest('Invalid X-Object-Manifest header')
538 raise ItemNotFound('Container does not exist')
542 s, h = backend.get_object_hashmap(request.user, v_account, src_container, x[0], x[1])
545 except NotAllowedError:
546 raise Unauthorized('Access denied')
548 raise ItemNotFound('Object does not exist')
550 raise ItemNotFound('Version does not exist')
553 s, h = backend.get_object_hashmap(request.user, v_account, v_container, v_object, version)
556 except NotAllowedError:
557 raise Unauthorized('Access denied')
559 raise ItemNotFound('Object does not exist')
561 raise ItemNotFound('Version does not exist')
563 # Reply with the hashmap.
564 if request.serialization != 'text':
566 hashmap = sum(hashmaps, [])
567 d = {'block_size': backend.block_size, 'block_hash': backend.hash_algorithm, 'bytes': size, 'hashes': hashmap}
568 if request.serialization == 'xml':
569 d['object'] = v_object
570 data = render_to_string('hashes.xml', d)
571 elif request.serialization == 'json':
574 response = HttpResponse(data, status=200)
575 put_object_headers(response, meta)
576 response['Content-Length'] = len(data)
579 return object_data_response(request, sizes, hashmaps, meta)
581 @api_method('PUT', format_allowed=True)
582 def object_write(request, v_account, v_container, v_object):
583 # Normal Response Codes: 201
584 # Error Response Codes: serviceUnavailable (503),
585 # unprocessableEntity (422),
586 # lengthRequired (411),
588 # itemNotFound (404),
589 # unauthorized (401),
591 copy_from = request.META.get('HTTP_X_COPY_FROM')
592 move_from = request.META.get('HTTP_X_MOVE_FROM')
593 if copy_from or move_from:
594 # TODO: Why is this required? Copy this ammount?
595 content_length = get_content_length(request)
599 src_container, src_name = split_container_object_string(move_from)
601 raise BadRequest('Invalid X-Move-From header')
602 copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=True)
605 src_container, src_name = split_container_object_string(copy_from)
607 raise BadRequest('Invalid X-Copy-From header')
608 copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=False)
609 return HttpResponse(status=201)
611 meta, permissions, public = get_object_headers(request)
613 if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
614 content_length = get_content_length(request)
615 # Should be BadRequest, but API says otherwise.
616 if 'Content-Type' not in meta:
617 raise LengthRequired('Missing Content-Type header')
619 if request.serialization == 'json':
621 sock = raw_input_socket(request)
622 for block in socket_read_iterator(sock, content_length, backend.block_size):
623 data = '%s%s' % (data, block)
625 if not hasattr(d, '__getitem__'):
626 raise BadRequest('Invalid data formating')
628 hashmap = d['hashes']
631 raise BadRequest('Invalid data formatting')
632 meta.update({'hash': hashmap_hash(hashmap)}) # Update ETag.
633 elif request.serialization == 'xml':
634 #TODO support for xml
635 raise BadRequest('Format xml is not supported')
640 sock = raw_input_socket(request)
641 for data in socket_read_iterator(sock, content_length, backend.block_size):
642 # TODO: Raise 408 (Request Timeout) if this takes too long.
643 # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
645 hashmap.append(backend.put_block(data))
648 meta['hash'] = md5.hexdigest().lower()
649 etag = request.META.get('HTTP_ETAG')
650 if etag and parse_etags(etag)[0].lower() != meta['hash']:
651 raise UnprocessableEntity('Object ETag does not match')
656 backend.update_object_hashmap(request.user, v_account, v_container, v_object, size, hashmap, meta, True, permissions)
657 except NotAllowedError:
658 raise Unauthorized('Access denied')
659 except IndexError, e:
660 payload = json.dumps(e.data)
663 raise ItemNotFound('Container does not exist')
665 raise BadRequest('Invalid sharing header')
666 except AttributeError:
667 raise Conflict('Sharing already set above or below this path in the hierarchy')
668 if public is not None:
670 backend.update_object_public(request.user, v_account, v_container, v_object, public)
671 except NotAllowedError:
672 raise Unauthorized('Access denied')
674 raise ItemNotFound('Object does not exist')
676 response = HttpResponse(content=payload, status=code)
677 response['ETag'] = meta['hash']
681 def object_copy(request, v_account, v_container, v_object):
682 # Normal Response Codes: 201
683 # Error Response Codes: serviceUnavailable (503),
684 # itemNotFound (404),
685 # unauthorized (401),
688 dest_path = request.META.get('HTTP_DESTINATION')
690 raise BadRequest('Missing Destination header')
692 dest_container, dest_name = split_container_object_string(dest_path)
694 raise BadRequest('Invalid Destination header')
695 copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=False)
696 return HttpResponse(status=201)
699 def object_move(request, v_account, v_container, v_object):
700 # Normal Response Codes: 201
701 # Error Response Codes: serviceUnavailable (503),
702 # itemNotFound (404),
703 # unauthorized (401),
706 dest_path = request.META.get('HTTP_DESTINATION')
708 raise BadRequest('Missing Destination header')
710 dest_container, dest_name = split_container_object_string(dest_path)
712 raise BadRequest('Invalid Destination header')
713 copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=True)
714 return HttpResponse(status=201)
717 def object_update(request, v_account, v_container, v_object):
718 # Normal Response Codes: 202, 204
719 # Error Response Codes: serviceUnavailable (503),
721 # itemNotFound (404),
722 # unauthorized (401),
725 meta, permissions, public = get_object_headers(request)
726 content_type = meta.get('Content-Type')
728 del(meta['Content-Type']) # Do not allow changing the Content-Type.
731 prev_meta = backend.get_object_meta(request.user, v_account, v_container, v_object)
732 except NotAllowedError:
733 raise Unauthorized('Access denied')
735 raise ItemNotFound('Object does not exist')
736 # If replacing, keep previous values of 'Content-Type' and 'hash'.
738 if 'update' in request.GET:
741 for k in ('Content-Type', 'hash'):
743 meta[k] = prev_meta[k]
745 # A Content-Type header indicates data updates.
746 if not content_type or content_type != 'application/octet-stream':
747 # Do permissions first, as it may fail easier.
748 if permissions is not None:
750 backend.update_object_permissions(request.user, v_account, v_container, v_object, permissions)
751 except NotAllowedError:
752 raise Unauthorized('Access denied')
754 raise ItemNotFound('Object does not exist')
756 raise BadRequest('Invalid sharing header')
757 except AttributeError:
758 raise Conflict('Sharing already set above or below this path in the hierarchy')
759 if public is not None:
761 backend.update_object_public(request.user, v_account, v_container, v_object, public)
762 except NotAllowedError:
763 raise Unauthorized('Access denied')
765 raise ItemNotFound('Object does not exist')
767 backend.update_object_meta(request.user, v_account, v_container, v_object, meta, replace)
768 except NotAllowedError:
769 raise Unauthorized('Access denied')
771 raise ItemNotFound('Object does not exist')
772 return HttpResponse(status=202)
774 # Single range update. Range must be in Content-Range.
775 # Based on: http://code.google.com/p/gears/wiki/ContentRangePostProposal
776 # (with the addition that '*' is allowed for the range - will append).
777 content_range = request.META.get('HTTP_CONTENT_RANGE')
778 if not content_range:
779 raise BadRequest('Missing Content-Range header')
780 ranges = get_content_range(request)
782 raise RangeNotSatisfiable('Invalid Content-Range header')
783 # Require either a Content-Length, or 'chunked' Transfer-Encoding.
785 if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
786 content_length = get_content_length(request)
789 size, hashmap = backend.get_object_hashmap(request.user, v_account, v_container, v_object)
790 except NotAllowedError:
791 raise Unauthorized('Access denied')
793 raise ItemNotFound('Object does not exist')
795 offset, length, total = ranges
799 raise RangeNotSatisfiable('Supplied offset is beyond object limits')
800 if length is None or content_length == -1:
801 length = content_length # Nevermind the error.
802 elif length != content_length:
803 raise BadRequest('Content length does not match range length')
804 if total is not None and (total != size or offset >= size or (length > 0 and offset + length >= size)):
805 raise RangeNotSatisfiable('Supplied range will change provided object limits')
807 sock = raw_input_socket(request)
809 for d in socket_read_iterator(sock, length, backend.block_size):
810 # TODO: Raise 408 (Request Timeout) if this takes too long.
811 # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
813 bytes = put_object_block(hashmap, data, offset)
817 put_object_block(hashmap, data, offset)
821 meta.update({'hash': hashmap_hash(hashmap)}) # Update ETag.
823 backend.update_object_hashmap(request.user, v_account, v_container, v_object, size, hashmap, meta, replace, permissions)
824 except NotAllowedError:
825 raise Unauthorized('Access denied')
827 raise ItemNotFound('Container does not exist')
829 raise BadRequest('Invalid sharing header')
830 except AttributeError:
831 raise Conflict('Sharing already set above or below this path in the hierarchy')
832 if public is not None:
834 backend.update_object_public(request.user, v_account, v_container, v_object, public)
835 except NotAllowedError:
836 raise Unauthorized('Access denied')
838 raise ItemNotFound('Object does not exist')
840 response = HttpResponse(status=204)
841 response['ETag'] = meta['hash']
844 @api_method('DELETE')
845 def object_delete(request, v_account, v_container, v_object):
846 # Normal Response Codes: 204
847 # Error Response Codes: serviceUnavailable (503),
848 # itemNotFound (404),
849 # unauthorized (401),
853 backend.delete_object(request.user, v_account, v_container, v_object)
854 except NotAllowedError:
855 raise Unauthorized('Access denied')
857 raise ItemNotFound('Object does not exist')
858 return HttpResponse(status=204)
861 def method_not_allowed(request):
862 raise BadRequest('Method not allowed')