+
+ # Evaluate conditions.
+ if request.META.get('HTTP_IF_MATCH') or request.META.get('HTTP_IF_NONE_MATCH'):
+ validate_matching_preconditions(request, prev_meta)
+
+ # If replacing, keep previous values of 'Content-Type' and 'ETag'.
+ replace = True
+ if 'update' in request.GET:
+ replace = False
+ if replace:
+ for k in ('Content-Type', 'ETag'):
+ if k in prev_meta:
+ meta[k] = prev_meta[k]
+
+ # A Content-Type or X-Source-Object header indicates data updates.
+ src_object = request.META.get('HTTP_X_SOURCE_OBJECT')
+ if (not content_type or content_type != 'application/octet-stream') and not src_object:
+ response = HttpResponse(status=202)
+
+ # Do permissions first, as it may fail easier.
+ if permissions is not None:
+ try:
+ request.backend.update_object_permissions(request.user_uniq,
+ v_account, v_container, v_object, permissions)
+ except NotAllowedError:
+ raise Forbidden('Not allowed')
+ except NameError:
+ raise ItemNotFound('Object does not exist')
+ except ValueError:
+ raise BadRequest('Invalid sharing header')
+ if public is not None:
+ try:
+ request.backend.update_object_public(request.user_uniq, v_account,
+ v_container, v_object, public)
+ except NotAllowedError:
+ raise Forbidden('Not allowed')
+ except NameError:
+ raise ItemNotFound('Object does not exist')
+ if meta or replace:
+ try:
+ version_id = request.backend.update_object_meta(request.user_uniq,
+ v_account, v_container, v_object, 'pithos', meta, replace)
+ except NotAllowedError:
+ raise Forbidden('Not allowed')
+ except NameError:
+ raise ItemNotFound('Object does not exist')
+ response['X-Object-Version'] = version_id
+
+ return response
+
+ # Single range update. Range must be in Content-Range.
+ # Based on: http://code.google.com/p/gears/wiki/ContentRangePostProposal
+ # (with the addition that '*' is allowed for the range - will append).
+ content_range = request.META.get('HTTP_CONTENT_RANGE')
+ if not content_range:
+ raise BadRequest('Missing Content-Range header')
+ ranges = get_content_range(request)
+ if not ranges:
+ raise RangeNotSatisfiable('Invalid Content-Range header')
+
+ try:
+ size, hashmap = request.backend.get_object_hashmap(request.user_uniq,
+ v_account, v_container, v_object)
+ except NotAllowedError:
+ raise Forbidden('Not allowed')
+ except NameError:
+ raise ItemNotFound('Object does not exist')
+
+ offset, length, total = ranges
+ if offset is None:
+ offset = size
+ elif offset > size:
+ raise RangeNotSatisfiable('Supplied offset is beyond object limits')
+ if src_object:
+ src_account = request.META.get('HTTP_X_SOURCE_ACCOUNT')
+ if not src_account:
+ src_account = request.user_uniq
+ src_container, src_name = split_container_object_string(src_object)
+ src_version = request.META.get('HTTP_X_SOURCE_VERSION')
+ try:
+ src_size, src_hashmap = request.backend.get_object_hashmap(request.user_uniq,
+ src_account, src_container, src_name, src_version)
+ except NotAllowedError:
+ raise Forbidden('Not allowed')
+ except NameError:
+ raise ItemNotFound('Source object does not exist')
+
+ if length is None:
+ length = src_size
+ elif length > src_size:
+ raise BadRequest('Object length is smaller than range length')
+ else:
+ # Require either a Content-Length, or 'chunked' Transfer-Encoding.
+ content_length = -1
+ if request.META.get('HTTP_TRANSFER_ENCODING') != 'chunked':
+ content_length = get_content_length(request)
+
+ if length is None:
+ length = content_length
+ else:
+ if content_length == -1:
+ # TODO: Get up to length bytes in chunks.
+ length = content_length
+ elif length != content_length:
+ raise BadRequest('Content length does not match range length')
+ if total is not None and (total != size or offset >= size or (length > 0 and offset + length >= size)):
+ raise RangeNotSatisfiable('Supplied range will change provided object limits')
+
+ dest_bytes = request.META.get('HTTP_X_OBJECT_BYTES')
+ if dest_bytes is not None:
+ dest_bytes = get_int_parameter(dest_bytes)
+ if dest_bytes is None:
+ raise BadRequest('Invalid X-Object-Bytes header')
+
+ if src_object:
+ if offset % request.backend.block_size == 0:
+ # Update the hashes only.
+ sbi = 0
+ while length > 0:
+ bi = int(offset / request.backend.block_size)
+ bl = min(length, request.backend.block_size)
+ if bi < len(hashmap):
+ if bl == request.backend.block_size:
+ hashmap[bi] = src_hashmap[sbi]
+ else:
+ data = request.backend.get_block(src_hashmap[sbi])
+ hashmap[bi] = request.backend.update_block(hashmap[bi],
+ data[:bl], 0)
+ else:
+ hashmap.append(src_hashmap[sbi])
+ offset += bl
+ length -= bl
+ sbi += 1
+ else:
+ data = ''
+ sbi = 0
+ while length > 0:
+ data += request.backend.get_block(src_hashmap[sbi])
+ if length < request.backend.block_size:
+ data = data[:length]
+ bytes = put_object_block(request, hashmap, data, offset)
+ offset += bytes
+ data = data[bytes:]
+ length -= bytes
+ sbi += 1
+ else:
+ data = ''
+ for d in socket_read_iterator(request, length,
+ request.backend.block_size):
+ # TODO: Raise 408 (Request Timeout) if this takes too long.
+ # TODO: Raise 499 (Client Disconnect) if a length is defined and we stop before getting this much data.
+ data += d
+ bytes = put_object_block(request, hashmap, data, offset)
+ offset += bytes
+ data = data[bytes:]
+ if len(data) > 0:
+ put_object_block(request, hashmap, data, offset)
+
+ if offset > size:
+ size = offset
+ if dest_bytes is not None and dest_bytes < size:
+ size = dest_bytes
+ hashmap = hashmap[:(int((size - 1) / request.backend.block_size) + 1)]
+ meta.update({'ETag': hashmap_md5(request, hashmap, size)}) # Update ETag.
+ try:
+ version_id = request.backend.update_object_hashmap(request.user_uniq,
+ v_account, v_container, v_object, size, hashmap,
+ 'pithos', meta, replace, permissions)
+ except NotAllowedError:
+ raise Forbidden('Not allowed')
+ except NameError:
+ raise ItemNotFound('Container does not exist')
+ except ValueError:
+ raise BadRequest('Invalid sharing header')
+ except QuotaError:
+ raise RequestEntityTooLarge('Quota exceeded')
+ if public is not None:
+ try:
+ request.backend.update_object_public(request.user_uniq, v_account,
+ v_container, v_object, public)
+ except NotAllowedError:
+ raise Forbidden('Not allowed')
+ except NameError:
+ raise ItemNotFound('Object does not exist')
+
+ response = HttpResponse(status=204)
+ response['ETag'] = meta['ETag']
+ response['X-Object-Version'] = version_id
+ return response