'Will handle the PUT of a SLO manifest.
Heads every object in manifest to check if is valid and if so will
save a manifest generated from the user input. Uses WSGIContext to
call self and start_response and returns a WSGI iterator.
:params req: a swob.Request with an obj in path
:raises HttpException: on errors'

def handle_multipart_put(self, req, start_response):

    try:
        (vrs, account, container, obj) = req.split_path(1, 4, True)
    except ValueError:
        return self.app(req.environ, start_response)
    if (req.content_length > self.max_manifest_size):
        raise HTTPRequestEntityTooLarge(('Manifest    File    >    %d    bytes' % self.max_manifest_size))
    if req.headers.get('X-Copy-From'):
        raise HTTPMethodNotAllowed('Multipart    Manifest    PUTs    cannot    be    COPY    requests')
    if ((req.content_length is None) and (req.headers.get('transfer-encoding', '').lower() != 'chunked')):
        raise HTTPLengthRequired(request=req)
    parsed_data = parse_and_validate_input(req.body_file.read(self.max_manifest_size), req.path)
    problem_segments = []
    if (len(parsed_data) > self.max_manifest_segments):
        raise HTTPRequestEntityTooLarge(('Number    of    segments    must    be    <=    %d' % self.max_manifest_segments))
    total_size = 0
    out_content_type = req.accept.best_match(ACCEPTABLE_FORMATS)
    if (not out_content_type):
        out_content_type = 'text/plain'
    data_for_storage = []
    path2indices = defaultdict(list)
    for (index, seg_dict) in enumerate(parsed_data):
        path2indices[seg_dict['path']].append(index)
    def do_head(obj_name):
        obj_path = '/'.join(['', vrs, account, get_valid_utf8_str(obj_name).lstrip('/')])
        sub_req = make_subrequest(req.environ, path=(obj_path + '?'), method='HEAD', headers={'x-auth-token': req.headers.get('x-auth-token')}, agent='%(orig)s    SLO    MultipartPUT', swift_source='SLO')
        return (obj_name, sub_req.get_response(self))
    def validate_seg_dict(seg_dict, head_seg_resp):
        if (not head_seg_resp.is_success):
            problem_segments.append([quote(obj_name), head_seg_resp.status])
            return (0, None)
        segment_length = head_seg_resp.content_length
        if seg_dict.get('range'):
            ranges = seg_dict['range'].ranges_for_length(head_seg_resp.content_length)
            if (not ranges):
                problem_segments.append([quote(obj_name), 'Unsatisfiable    Range'])
            elif (ranges == [(0, head_seg_resp.content_length)]):
                del seg_dict['range']
                segment_length = head_seg_resp.content_length
            else:
                rng = ranges[0]
                seg_dict['range'] = ('%d-%d' % (rng[0], (rng[1] - 1)))
                segment_length = (rng[1] - rng[0])
        if (segment_length < 1):
            problem_segments.append([quote(obj_name), 'Too    small;    each    segment    must    be    at    least    1    byte.'])
        if ((seg_dict.get('size_bytes') is not None) and (seg_dict['size_bytes'] != head_seg_resp.content_length)):
            problem_segments.append([quote(obj_name), 'Size    Mismatch'])
        if ((seg_dict.get('etag') is not None) and (seg_dict['etag'] != head_seg_resp.etag)):
            problem_segments.append([quote(obj_name), 'Etag    Mismatch'])
        if head_seg_resp.last_modified:
            last_modified = head_seg_resp.last_modified
        else:
            last_modified = datetime.now()
        last_modified_formatted = last_modified.strftime('%Y-%m-%dT%H:%M:%S.%f')
        seg_data = {'name': ('/' + seg_dict['path'].lstrip('/')), 'bytes': head_seg_resp.content_length, 'hash': head_seg_resp.etag, 'content_type': head_seg_resp.content_type, 'last_modified': last_modified_formatted}
        if seg_dict.get('range'):
            seg_data['range'] = seg_dict['range']
        if config_true_value(head_seg_resp.headers.get('X-Static-Large-Object')):
            seg_data['sub_slo'] = True
        return (segment_length, seg_data)
    data_for_storage = ([None] * len(parsed_data))
    with StreamingPile(self.concurrency) as pile:
        for (obj_name, resp) in pile.asyncstarmap(do_head, ((path,) for path in path2indices)):
            for i in path2indices[obj_name]:
                (segment_length, seg_data) = validate_seg_dict(parsed_data[i], resp)
                data_for_storage[i] = seg_data
                total_size += segment_length
    if problem_segments:
        resp_body = get_response_body(out_content_type, {}, problem_segments)
        raise HTTPBadRequest(resp_body, content_type=out_content_type)
    slo_etag = md5()
    for seg_data in data_for_storage:
        if seg_data.get('range'):
            slo_etag.update(('%s:%s;' % (seg_data['hash'], seg_data['range'])))
        else:
            slo_etag.update(seg_data['hash'])
    slo_etag = slo_etag.hexdigest()
    client_etag = req.headers.get('Etag')
    if (client_etag and (client_etag.strip('"') != slo_etag)):
        raise HTTPUnprocessableEntity(request=req)
    json_data = json.dumps(data_for_storage)
    if six.PY3:
        json_data = json_data.encode('utf-8')
    req.body = json_data
    req.headers.update({SYSMETA_SLO_ETAG: slo_etag, SYSMETA_SLO_SIZE: total_size, 'X-Static-Large-Object': 'True', 'Etag': md5(json_data).hexdigest()})
    env = req.environ
    if (not env.get('CONTENT_TYPE')):
        (guessed_type, _junk) = mimetypes.guess_type(req.path_info)
        env['CONTENT_TYPE'] = (guessed_type or 'application/octet-stream')
    env['swift.content_type_overridden'] = True
    env['CONTENT_TYPE'] += (';swift_bytes=%d' % total_size)
    def start_response_wrapper(status, headers, exc_info=None):
        for (i, (header, _value)) in enumerate(headers):
            if (header.lower() == 'etag'):
                headers[i] = ('Etag', ('"%s"' % slo_etag))
                break
        return start_response(status, headers, exc_info)
    return self.app(env, start_response_wrapper)
