'Handle HTTP PUT requests for the Swift Object Server.'

@public
@timing_stats()
def PUT(self, request):

    (device, partition, account, container, obj, policy) = get_name_and_placement(request, 5, 5, True)
    req_timestamp = valid_timestamp(request)
    error_response = check_object_creation(request, obj)
    if error_response:
        return error_response
    new_delete_at = int((request.headers.get('X-Delete-At') or 0))
    try:
        fsize = request.message_length()
    except ValueError as e:
        return HTTPBadRequest(body=str(e), request=request, content_type='text/plain')
    if (fsize is None):
        fsize = request.headers.get('X-Backend-Obj-Content-Length')
        if (fsize is not None):
            try:
                fsize = int(fsize)
            except ValueError as e:
                return HTTPBadRequest(body=str(e), request=request, content_type='text/plain')
    frag_index = request.headers.get('X-Backend-Ssync-Frag-Index')
    next_part_power = request.headers.get('X-Backend-Next-Part-Power')
    try:
        disk_file = self.get_diskfile(device, partition, account, container, obj, policy=policy, frag_index=frag_index, next_part_power=next_part_power)
    except DiskFileDeviceUnavailable:
        return HTTPInsufficientStorage(drive=device, request=request)
    try:
        orig_metadata = disk_file.read_metadata()
        orig_timestamp = disk_file.data_timestamp
    except DiskFileXattrNotSupported:
        return HTTPInsufficientStorage(drive=device, request=request)
    except DiskFileDeleted as e:
        orig_metadata = {}
        orig_timestamp = e.timestamp
    except (DiskFileNotExist, DiskFileQuarantined):
        orig_metadata = {}
        orig_timestamp = Timestamp(0)
    if ((request.if_none_match is not None) and orig_metadata):
        if ('*' in request.if_none_match):
            return HTTPPreconditionFailed(request=request)
        if (orig_metadata.get('ETag') in request.if_none_match):
            return HTTPPreconditionFailed(request=request)
    if (orig_timestamp >= req_timestamp):
        return HTTPConflict(request=request, headers={'X-Backend-Timestamp': orig_timestamp.internal})
    orig_delete_at = int((orig_metadata.get('X-Delete-At') or 0))
    upload_expiration = (time.time() + self.max_upload_time)
    etag = md5()
    elapsed_time = 0
    try:
        with disk_file.create(size=fsize) as writer:
            upload_size = 0
            have_metadata_footer = False
            use_multiphase_commit = False
            mime_documents_iter = iter([])
            obj_input = request.environ['wsgi.input']
            hundred_continue_headers = []
            if config_true_value(request.headers.get('X-Backend-Obj-Multiphase-Commit')):
                use_multiphase_commit = True
                hundred_continue_headers.append(('X-Obj-Multiphase-Commit', 'yes'))
            if config_true_value(request.headers.get('X-Backend-Obj-Metadata-Footer')):
                have_metadata_footer = True
                hundred_continue_headers.append(('X-Obj-Metadata-Footer', 'yes'))
            if (have_metadata_footer or use_multiphase_commit):
                obj_input.set_hundred_continue_response_headers(hundred_continue_headers)
                mime_boundary = request.headers.get('X-Backend-Obj-Multipart-Mime-Boundary')
                if (not mime_boundary):
                    return HTTPBadRequest('no    MIME    boundary')
                try:
                    with ChunkReadTimeout(self.client_timeout):
                        mime_documents_iter = iter_mime_headers_and_bodies(request.environ['wsgi.input'], mime_boundary, self.network_chunk_size)
                        (_junk_hdrs, obj_input) = next(mime_documents_iter)
                except ChunkReadError:
                    return HTTPClientDisconnect(request=request)
                except ChunkReadTimeout:
                    return HTTPRequestTimeout(request=request)
            timeout_reader = self._make_timeout_reader(obj_input)
            try:
                for chunk in iter(timeout_reader, ''):
                    start_time = time.time()
                    if (start_time > upload_expiration):
                        self.logger.increment('PUT.timeouts')
                        return HTTPRequestTimeout(request=request)
                    etag.update(chunk)
                    upload_size = writer.write(chunk)
                    elapsed_time += (time.time() - start_time)
            except ChunkReadError:
                return HTTPClientDisconnect(request=request)
            except ChunkReadTimeout:
                return HTTPRequestTimeout(request=request)
            if upload_size:
                self.logger.transfer_rate((('PUT.' + device) + '.timing'), elapsed_time, upload_size)
            if ((fsize is not None) and (fsize != upload_size)):
                return HTTPClientDisconnect(request=request)
            footer_meta = {}
            if have_metadata_footer:
                footer_meta = self._read_metadata_footer(mime_documents_iter)
            request_etag = (footer_meta.get('etag') or request.headers.get('etag', '')).lower()
            etag = etag.hexdigest()
            if (request_etag and (request_etag != etag)):
                return HTTPUnprocessableEntity(request=request)
            metadata = {'X-Timestamp': request.timestamp.internal, 'Content-Type': request.headers['content-type'], 'ETag': etag, 'Content-Length': str(upload_size)}
            metadata.update((val for val in request.headers.items() if (is_sys_or_user_meta('object', val[0]) or is_object_transient_sysmeta(val[0]))))
            metadata.update((val for val in footer_meta.items() if (is_sys_or_user_meta('object', val[0]) or is_object_transient_sysmeta(val[0]))))
            headers_to_copy = (request.headers.get('X-Backend-Replication-Headers', '').split() + list(self.allowed_headers))
            for header_key in headers_to_copy:
                if (header_key in request.headers):
                    header_caps = header_key.title()
                    metadata[header_caps] = request.headers[header_key]
            writer.put(metadata)
            if use_multiphase_commit:
                request.environ['wsgi.input'].send_hundred_continue_response()
                if (not self._read_put_commit_message(mime_documents_iter)):
                    return HTTPServerError(request=request)
            writer.commit(request.timestamp)
            try:
                while True:
                    with ChunkReadTimeout(self.client_timeout):
                        (_junk_hdrs, _junk_body) = next(mime_documents_iter)
                    drain(_junk_body, self.network_chunk_size, self.client_timeout)
            except ChunkReadError:
                raise HTTPClientDisconnect()
            except ChunkReadTimeout:
                raise HTTPRequestTimeout()
            except StopIteration:
                pass
    except (DiskFileXattrNotSupported, DiskFileNoSpace):
        return HTTPInsufficientStorage(drive=device, request=request)
    if (orig_delete_at != new_delete_at):
        if new_delete_at:
            self.delete_at_update('PUT', new_delete_at, account, container, obj, request, device, policy)
        if orig_delete_at:
            self.delete_at_update('DELETE', orig_delete_at, account, container, obj, request, device, policy)
    update_headers = HeaderKeyDict({'x-size': metadata['Content-Length'], 'x-content-type': metadata['Content-Type'], 'x-timestamp': metadata['X-Timestamp'], 'x-etag': metadata['ETag']})
    self._check_container_override(update_headers, request.headers, footer_meta)
    self.container_update('PUT', account, container, obj, request, update_headers, device, policy)
    return HTTPCreated(request=request, etag=etag)
