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 if request.META.get('CONTENT_TYPE', '').startswith('multipart/form-data'):
102 return object_write_form(request, v_account, v_container, v_object)
103 return object_update(request, v_account, v_container, v_object)
104 elif request.method == 'DELETE':
105 return object_delete(request, v_account, v_container, v_object)
107 return method_not_allowed(request)
110 def authenticate(request):
111 # Normal Response Codes: 204
112 # Error Response Codes: serviceUnavailable (503),
113 # unauthorized (401),
116 x_auth_user = request.META.get('HTTP_X_AUTH_USER')
117 x_auth_key = request.META.get('HTTP_X_AUTH_KEY')
118 if not x_auth_user or not x_auth_key:
119 raise BadRequest('Missing X-Auth-User or X-Auth-Key header')
120 response = HttpResponse(status=204)
121 inv_auth_tokens = dict((v, k) for k, v in settings.AUTH_TOKENS.items())
122 response['X-Auth-Token'] = inv_auth_tokens.get(x_auth_user, '0000')
123 response['X-Storage-Url'] = os.path.join(request.build_absolute_uri(),
128 def account_meta(request, v_account):
129 # Normal Response Codes: 204
130 # Error Response Codes: serviceUnavailable (503),
131 # unauthorized (401),
134 until = get_int_parameter(request.GET.get('until'))
136 meta = backend.get_account_meta(request.user, v_account, until)
137 groups = backend.get_account_groups(request.user, v_account)
138 except NotAllowedError:
139 raise Unauthorized('Access denied')
141 response = HttpResponse(status=204)
142 put_account_headers(response, meta, groups)
146 def account_update(request, v_account):
147 # Normal Response Codes: 202
148 # Error Response Codes: serviceUnavailable (503),
149 # unauthorized (401),
152 meta, groups = get_account_headers(request)
154 if 'update' in request.GET:
158 backend.update_account_groups(request.user, v_account, groups, replace)
159 except NotAllowedError:
160 raise Unauthorized('Access denied')
162 raise BadRequest('Invalid groups header')
164 backend.update_account_meta(request.user, v_account, meta, replace)
165 except NotAllowedError:
166 raise Unauthorized('Access denied')
167 return HttpResponse(status=202)
169 @api_method('GET', format_allowed=True)
170 def container_list(request, v_account):
171 # Normal Response Codes: 200, 204
172 # Error Response Codes: serviceUnavailable (503),
173 # itemNotFound (404),
174 # unauthorized (401),
177 until = get_int_parameter(request.GET.get('until'))
179 meta = backend.get_account_meta(request.user, v_account, until)
180 groups = backend.get_account_groups(request.user, v_account)
181 except NotAllowedError:
182 raise Unauthorized('Access denied')
184 validate_modification_preconditions(request, meta)
186 response = HttpResponse()
187 put_account_headers(response, meta, groups)
189 marker = request.GET.get('marker')
190 limit = request.GET.get('limit')
200 containers = backend.list_containers(request.user, v_account, marker, limit, until)
201 except NotAllowedError:
202 raise Unauthorized('Access denied')
206 if request.serialization == 'text':
207 if len(containers) == 0:
208 # The cloudfiles python bindings expect 200 if json/xml.
209 response.status_code = 204
211 response.status_code = 200
212 response.content = '\n'.join([x[0] for x in containers]) + '\n'
219 meta = backend.get_container_meta(request.user, v_account, x[0], until)
220 policy = backend.get_container_policy(request.user, v_account, x[0])
221 except NotAllowedError:
222 raise Unauthorized('Access denied')
226 for k, v in policy.iteritems():
227 meta['X-Container-Policy-' + k] = v
228 container_meta.append(printable_header_dict(meta))
229 if request.serialization == 'xml':
230 data = render_to_string('containers.xml', {'account': v_account, 'containers': container_meta})
231 elif request.serialization == 'json':
232 data = json.dumps(container_meta)
233 response.status_code = 200
234 response.content = data
238 def container_meta(request, v_account, v_container):
239 # Normal Response Codes: 204
240 # Error Response Codes: serviceUnavailable (503),
241 # itemNotFound (404),
242 # unauthorized (401),
245 until = get_int_parameter(request.GET.get('until'))
247 meta = backend.get_container_meta(request.user, v_account, v_container, until)
248 meta['object_meta'] = backend.list_object_meta(request.user, v_account, v_container, until)
249 policy = backend.get_container_policy(request.user, v_account, v_container)
250 except NotAllowedError:
251 raise Unauthorized('Access denied')
253 raise ItemNotFound('Container does not exist')
255 response = HttpResponse(status=204)
256 put_container_headers(response, meta, policy)
260 def container_create(request, v_account, v_container):
261 # Normal Response Codes: 201, 202
262 # Error Response Codes: serviceUnavailable (503),
263 # itemNotFound (404),
264 # unauthorized (401),
267 meta, policy = get_container_headers(request)
270 backend.put_container(request.user, v_account, v_container, policy)
272 except NotAllowedError:
273 raise Unauthorized('Access denied')
279 backend.update_container_meta(request.user, v_account, v_container, meta, replace=True)
280 except NotAllowedError:
281 raise Unauthorized('Access denied')
283 raise ItemNotFound('Container does not exist')
285 return HttpResponse(status=ret)
288 def container_update(request, v_account, v_container):
289 # Normal Response Codes: 202
290 # Error Response Codes: serviceUnavailable (503),
291 # itemNotFound (404),
292 # unauthorized (401),
295 meta, policy = get_container_headers(request)
297 if 'update' in request.GET:
301 backend.update_container_policy(request.user, v_account, v_container, policy, replace)
302 except NotAllowedError:
303 raise Unauthorized('Access denied')
305 raise ItemNotFound('Container does not exist')
307 raise BadRequest('Invalid policy header')
309 backend.update_container_meta(request.user, v_account, v_container, meta, replace)
310 except NotAllowedError:
311 raise Unauthorized('Access denied')
313 raise ItemNotFound('Container does not exist')
314 return HttpResponse(status=202)
316 @api_method('DELETE')
317 def container_delete(request, v_account, v_container):
318 # Normal Response Codes: 204
319 # Error Response Codes: serviceUnavailable (503),
321 # itemNotFound (404),
322 # unauthorized (401),
326 backend.delete_container(request.user, v_account, v_container)
327 except NotAllowedError:
328 raise Unauthorized('Access denied')
330 raise ItemNotFound('Container does not exist')
332 raise Conflict('Container is not empty')
333 return HttpResponse(status=204)
335 @api_method('GET', format_allowed=True)
336 def object_list(request, v_account, v_container):
337 # Normal Response Codes: 200, 204
338 # Error Response Codes: serviceUnavailable (503),
339 # itemNotFound (404),
340 # unauthorized (401),
343 until = get_int_parameter(request.GET.get('until'))
345 meta = backend.get_container_meta(request.user, v_account, v_container, until)
346 meta['object_meta'] = backend.list_object_meta(request.user, v_account, v_container, until)
347 policy = backend.get_container_policy(request.user, v_account, v_container)
348 except NotAllowedError:
349 raise Unauthorized('Access denied')
351 raise ItemNotFound('Container does not exist')
353 validate_modification_preconditions(request, meta)
355 response = HttpResponse()
356 put_container_headers(response, meta, policy)
358 path = request.GET.get('path')
359 prefix = request.GET.get('prefix')
360 delimiter = request.GET.get('delimiter')
362 # Path overrides prefix and delimiter.
370 if prefix and delimiter:
371 prefix = prefix + delimiter
374 prefix = prefix.lstrip('/')
376 marker = request.GET.get('marker')
377 limit = request.GET.get('limit')
386 keys = request.GET.get('meta')
388 keys = keys.split(',')
389 keys = [format_header_key('X-Object-Meta-' + x.strip()) for x in keys if x.strip() != '']
394 objects = backend.list_objects(request.user, v_account, v_container, prefix, delimiter, marker, limit, virtual, keys, until)
395 except NotAllowedError:
396 raise Unauthorized('Access denied')
398 raise ItemNotFound('Container does not exist')
400 if request.serialization == 'text':
401 if len(objects) == 0:
402 # The cloudfiles python bindings expect 200 if json/xml.
403 response.status_code = 204
405 response.status_code = 200
406 response.content = '\n'.join([x[0] for x in objects]) + '\n'
412 # Virtual objects/directories.
413 object_meta.append({'subdir': x[0]})
416 meta = backend.get_object_meta(request.user, v_account, v_container, x[0], x[1])
418 permissions = backend.get_object_permissions(request.user, v_account, v_container, x[0])
419 public = backend.get_object_public(request.user, v_account, v_container, x[0])
423 except NotAllowedError:
424 raise Unauthorized('Access denied')
428 update_sharing_meta(permissions, v_account, v_container, x[0], meta)
429 update_public_meta(public, meta)
430 object_meta.append(printable_header_dict(meta))
431 if request.serialization == 'xml':
432 data = render_to_string('objects.xml', {'container': v_container, 'objects': object_meta})
433 elif request.serialization == 'json':
434 data = json.dumps(object_meta)
435 response.status_code = 200
436 response.content = data
440 def object_meta(request, v_account, v_container, v_object):
441 # Normal Response Codes: 204
442 # Error Response Codes: serviceUnavailable (503),
443 # itemNotFound (404),
444 # unauthorized (401),
447 version = request.GET.get('version')
449 meta = backend.get_object_meta(request.user, v_account, v_container, v_object, version)
451 permissions = backend.get_object_permissions(request.user, v_account, v_container, v_object)
452 public = backend.get_object_public(request.user, v_account, v_container, v_object)
456 except NotAllowedError:
457 raise Unauthorized('Access denied')
459 raise ItemNotFound('Object does not exist')
461 raise ItemNotFound('Version does not exist')
463 update_manifest_meta(request, v_account, meta)
464 update_sharing_meta(permissions, v_account, v_container, v_object, meta)
465 update_public_meta(public, meta)
467 response = HttpResponse(status=200)
468 put_object_headers(response, meta)
471 @api_method('GET', format_allowed=True)
472 def object_read(request, v_account, v_container, v_object):
473 # Normal Response Codes: 200, 206
474 # Error Response Codes: serviceUnavailable (503),
475 # rangeNotSatisfiable (416),
476 # preconditionFailed (412),
477 # itemNotFound (404),
478 # unauthorized (401),
482 version = request.GET.get('version')
484 # Reply with the version list. Do this first, as the object may be deleted.
485 if version == 'list':
486 if request.serialization == 'text':
487 raise BadRequest('No format specified for version list.')
490 v = backend.list_versions(request.user, v_account, v_container, v_object)
491 except NotAllowedError:
492 raise Unauthorized('Access denied')
494 if request.serialization == 'xml':
495 d['object'] = v_object
496 data = render_to_string('versions.xml', d)
497 elif request.serialization == 'json':
500 response = HttpResponse(data, status=200)
501 response['Content-Length'] = len(data)
505 meta = backend.get_object_meta(request.user, v_account, v_container, v_object, version)
507 permissions = backend.get_object_permissions(request.user, v_account, v_container, v_object)
508 public = backend.get_object_public(request.user, v_account, v_container, v_object)
512 except NotAllowedError:
513 raise Unauthorized('Access denied')
515 raise ItemNotFound('Object does not exist')
517 raise ItemNotFound('Version does not exist')
519 update_manifest_meta(request, v_account, meta)
520 update_sharing_meta(permissions, v_account, v_container, v_object, meta)
521 update_public_meta(public, meta)
523 # Evaluate conditions.
524 validate_modification_preconditions(request, meta)
526 validate_matching_preconditions(request, meta)
528 response = HttpResponse(status=304)
529 response['ETag'] = meta['hash']
534 if 'X-Object-Manifest' in meta:
536 src_container, src_name = split_container_object_string('/' + meta['X-Object-Manifest'])
537 objects = backend.list_objects(request.user, v_account, src_container, prefix=src_name, virtual=False)
538 except NotAllowedError:
539 raise Unauthorized('Access denied')
541 raise BadRequest('Invalid X-Object-Manifest header')
543 raise ItemNotFound('Container does not exist')
547 s, h = backend.get_object_hashmap(request.user, v_account, src_container, x[0], x[1])
550 except NotAllowedError:
551 raise Unauthorized('Access denied')
553 raise ItemNotFound('Object does not exist')
555 raise ItemNotFound('Version does not exist')
558 s, h = backend.get_object_hashmap(request.user, v_account, v_container, v_object, version)
561 except NotAllowedError:
562 raise Unauthorized('Access denied')
564 raise ItemNotFound('Object does not exist')
566 raise ItemNotFound('Version does not exist')
568 # Reply with the hashmap.
569 if request.serialization != 'text':
571 hashmap = sum(hashmaps, [])
572 d = {'block_size': backend.block_size, 'block_hash': backend.hash_algorithm, 'bytes': size, 'hashes': hashmap}
573 if request.serialization == 'xml':
574 d['object'] = v_object
575 data = render_to_string('hashes.xml', d)
576 elif request.serialization == 'json':
579 response = HttpResponse(data, status=200)
580 put_object_headers(response, meta)
581 response['Content-Length'] = len(data)
584 return object_data_response(request, sizes, hashmaps, meta)
586 @api_method('PUT', format_allowed=True)
587 def object_write(request, v_account, v_container, v_object):
588 # Normal Response Codes: 201
589 # Error Response Codes: serviceUnavailable (503),
590 # unprocessableEntity (422),
591 # lengthRequired (411),
593 # itemNotFound (404),
594 # unauthorized (401),
597 if not request.GET.get('format'):
598 request.serialization = 'text'
600 copy_from = request.META.get('HTTP_X_COPY_FROM')
601 move_from = request.META.get('HTTP_X_MOVE_FROM')
602 if copy_from or move_from:
603 content_length = get_content_length(request) # Required by the API.
607 src_container, src_name = split_container_object_string(move_from)
609 raise BadRequest('Invalid X-Move-From header')
610 copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=True)
613 src_container, src_name = split_container_object_string(copy_from)
615 raise BadRequest('Invalid X-Copy-From header')
616 copy_or_move_object(request, v_account, src_container, src_name, v_container, v_object, move=False)
617 return HttpResponse(status=201)
619 meta, permissions, public = get_object_headers(request)
621 if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
622 content_length = get_content_length(request)
623 # Should be BadRequest, but API says otherwise.
624 if 'Content-Type' not in meta:
625 raise LengthRequired('Missing Content-Type header')
627 if request.serialization == 'json':
629 sock = raw_input_socket(request)
630 for block in socket_read_iterator(sock, content_length, backend.block_size):
631 data = '%s%s' % (data, block)
633 if not hasattr(d, '__getitem__'):
634 raise BadRequest('Invalid data formating')
636 hashmap = d['hashes']
639 raise BadRequest('Invalid data formatting')
640 meta.update({'hash': hashmap_hash(hashmap)}) # Update ETag.
641 elif request.serialization == 'xml':
643 raise BadRequest('Format xml is not supported')
648 sock = raw_input_socket(request)
649 for data in socket_read_iterator(sock, content_length, backend.block_size):
650 # TODO: Raise 408 (Request Timeout) if this takes too long.
651 # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
653 hashmap.append(backend.put_block(data))
656 meta['hash'] = md5.hexdigest().lower()
657 etag = request.META.get('HTTP_ETAG')
658 if etag and parse_etags(etag)[0].lower() != meta['hash']:
659 raise UnprocessableEntity('Object ETag does not match')
662 backend.update_object_hashmap(request.user, v_account, v_container, v_object, size, hashmap, meta, True, permissions)
663 except NotAllowedError:
664 raise Unauthorized('Access denied')
665 except IndexError, e:
666 raise Conflict(json.dumps(e.data))
668 raise ItemNotFound('Container does not exist')
670 raise BadRequest('Invalid sharing header')
671 except AttributeError, e:
672 raise Conflict(json.dumps(e.data))
673 if public is not None:
675 backend.update_object_public(request.user, v_account, v_container, v_object, public)
676 except NotAllowedError:
677 raise Unauthorized('Access denied')
679 raise ItemNotFound('Object does not exist')
681 response = HttpResponse(status=201)
682 response['ETag'] = meta['hash']
686 def object_write_form(request, v_account, v_container, v_object):
687 # Normal Response Codes: 201
688 # Error Response Codes: serviceUnavailable (503),
689 # itemNotFound (404),
690 # unauthorized (401),
693 if not request.FILES.has_key('X-Object-Data'):
694 raise BadRequest('Missing X-Object-Data field')
695 file = request.FILES['X-Object-Data']
698 meta['Content-Type'] = file.content_type
703 for data in file.chunks(backend.block_size):
705 hashmap.append(backend.put_block(data))
708 meta['hash'] = md5.hexdigest().lower()
711 backend.update_object_hashmap(request.user, v_account, v_container, v_object, size, hashmap, meta, True)
712 except NotAllowedError:
713 raise Unauthorized('Access denied')
715 raise ItemNotFound('Container does not exist')
717 response = HttpResponse(status=201)
718 response['ETag'] = meta['hash']
722 def object_copy(request, v_account, v_container, v_object):
723 # Normal Response Codes: 201
724 # Error Response Codes: serviceUnavailable (503),
725 # itemNotFound (404),
726 # unauthorized (401),
729 dest_path = request.META.get('HTTP_DESTINATION')
731 raise BadRequest('Missing Destination header')
733 dest_container, dest_name = split_container_object_string(dest_path)
735 raise BadRequest('Invalid Destination header')
736 copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=False)
737 return HttpResponse(status=201)
740 def object_move(request, v_account, v_container, v_object):
741 # Normal Response Codes: 201
742 # Error Response Codes: serviceUnavailable (503),
743 # itemNotFound (404),
744 # unauthorized (401),
747 dest_path = request.META.get('HTTP_DESTINATION')
749 raise BadRequest('Missing Destination header')
751 dest_container, dest_name = split_container_object_string(dest_path)
753 raise BadRequest('Invalid Destination header')
754 copy_or_move_object(request, v_account, v_container, v_object, dest_container, dest_name, move=True)
755 return HttpResponse(status=201)
758 def object_update(request, v_account, v_container, v_object):
759 # Normal Response Codes: 202, 204
760 # Error Response Codes: serviceUnavailable (503),
762 # itemNotFound (404),
763 # unauthorized (401),
766 meta, permissions, public = get_object_headers(request)
767 content_type = meta.get('Content-Type')
769 del(meta['Content-Type']) # Do not allow changing the Content-Type.
772 prev_meta = backend.get_object_meta(request.user, v_account, v_container, v_object)
773 except NotAllowedError:
774 raise Unauthorized('Access denied')
776 raise ItemNotFound('Object does not exist')
777 # If replacing, keep previous values of 'Content-Type' and 'hash'.
779 if 'update' in request.GET:
782 for k in ('Content-Type', 'hash'):
784 meta[k] = prev_meta[k]
786 # A Content-Type or X-Source-Object header indicates data updates.
787 src_object = request.META.get('HTTP_X_SOURCE_OBJECT')
788 if (not content_type or content_type != 'application/octet-stream') and not src_object:
789 # Do permissions first, as it may fail easier.
790 if permissions is not None:
792 backend.update_object_permissions(request.user, v_account, v_container, v_object, permissions)
793 except NotAllowedError:
794 raise Unauthorized('Access denied')
796 raise ItemNotFound('Object does not exist')
798 raise BadRequest('Invalid sharing header')
799 except AttributeError, e:
800 raise Conflict(json.dumps(e.data))
801 if public is not None:
803 backend.update_object_public(request.user, v_account, v_container, v_object, public)
804 except NotAllowedError:
805 raise Unauthorized('Access denied')
807 raise ItemNotFound('Object does not exist')
809 backend.update_object_meta(request.user, v_account, v_container, v_object, meta, replace)
810 except NotAllowedError:
811 raise Unauthorized('Access denied')
813 raise ItemNotFound('Object does not exist')
814 return HttpResponse(status=202)
816 # Single range update. Range must be in Content-Range.
817 # Based on: http://code.google.com/p/gears/wiki/ContentRangePostProposal
818 # (with the addition that '*' is allowed for the range - will append).
819 content_range = request.META.get('HTTP_CONTENT_RANGE')
820 if not content_range:
821 raise BadRequest('Missing Content-Range header')
822 ranges = get_content_range(request)
824 raise RangeNotSatisfiable('Invalid Content-Range header')
827 size, hashmap = backend.get_object_hashmap(request.user, v_account, v_container, v_object)
828 except NotAllowedError:
829 raise Unauthorized('Access denied')
831 raise ItemNotFound('Object does not exist')
833 offset, length, total = ranges
837 raise RangeNotSatisfiable('Supplied offset is beyond object limits')
839 src_container, src_name = split_container_object_string(src_object)
840 src_version = request.META.get('HTTP_X_SOURCE_VERSION')
842 src_size, src_hashmap = backend.get_object_hashmap(request.user, v_account, src_container, src_name, src_version)
843 except NotAllowedError:
844 raise Unauthorized('Access denied')
846 raise ItemNotFound('Source object does not exist')
850 elif length > src_size:
851 raise BadRequest('Object length is smaller than range length')
853 # Require either a Content-Length, or 'chunked' Transfer-Encoding.
855 if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
856 content_length = get_content_length(request)
859 length = content_length
861 if content_length == -1:
862 # TODO: Get up to length bytes in chunks.
863 length = content_length
864 elif length != content_length:
865 raise BadRequest('Content length does not match range length')
866 if total is not None and (total != size or offset >= size or (length > 0 and offset + length >= size)):
867 raise RangeNotSatisfiable('Supplied range will change provided object limits')
869 dest_bytes = request.META.get('HTTP_X_OBJECT_BYTES')
870 if dest_bytes is not None:
871 dest_bytes = get_int_parameter(dest_bytes)
872 if dest_bytes is None:
873 raise BadRequest('Invalid X-Object-Bytes header')
876 if offset % backend.block_size == 0:
877 # Update the hashes only.
880 bi = int(offset / backend.block_size)
881 bl = min(length, backend.block_size)
882 if bi < len(hashmap):
883 if bl == backend.block_size:
884 hashmap[bi] = src_hashmap[sbi]
886 data = backend.get_block(src_hashmap[sbi])
887 hashmap[bi] = backend.update_block(hashmap[bi], data[:bl], 0)
889 hashmap.append(src_hashmap[sbi])
897 data += backend.get_block(src_hashmap[sbi])
898 if length < backend.block_size:
900 bytes = put_object_block(hashmap, data, offset)
906 sock = raw_input_socket(request)
908 for d in socket_read_iterator(sock, length, backend.block_size):
909 # TODO: Raise 408 (Request Timeout) if this takes too long.
910 # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
912 bytes = put_object_block(hashmap, data, offset)
916 put_object_block(hashmap, data, offset)
920 if dest_bytes is not None and dest_bytes < size:
922 hashmap = hashmap[:(int((size - 1) / backend.block_size) + 1)]
923 meta.update({'hash': hashmap_hash(hashmap)}) # Update ETag.
925 backend.update_object_hashmap(request.user, v_account, v_container, v_object, size, hashmap, meta, replace, permissions)
926 except NotAllowedError:
927 raise Unauthorized('Access denied')
929 raise ItemNotFound('Container does not exist')
931 raise BadRequest('Invalid sharing header')
932 except AttributeError, e:
933 raise Conflict(json.dumps(e.data))
934 if public is not None:
936 backend.update_object_public(request.user, v_account, v_container, v_object, public)
937 except NotAllowedError:
938 raise Unauthorized('Access denied')
940 raise ItemNotFound('Object does not exist')
942 response = HttpResponse(status=204)
943 response['ETag'] = meta['hash']
946 @api_method('DELETE')
947 def object_delete(request, v_account, v_container, v_object):
948 # Normal Response Codes: 204
949 # Error Response Codes: serviceUnavailable (503),
950 # itemNotFound (404),
951 # unauthorized (401),
955 backend.delete_object(request.user, v_account, v_container, v_object)
956 except NotAllowedError:
957 raise Unauthorized('Access denied')
959 raise ItemNotFound('Object does not exist')
960 return HttpResponse(status=204)
963 def method_not_allowed(request):
964 raise BadRequest('Method not allowed')