Exemplo n.º 1
0
def _validate_and_prep_request_headers(req):
    """
    Validate that the value from x-symlink-target header is well formatted
    and that the x-symlink-target-etag header (if present) does not contain
    problematic characters. We assume the caller ensures that
    x-symlink-target header is present in req.headers.

    :param req: HTTP request object
    :returns: a tuple, the full versioned path to the object (as a WSGI string)
              and the X-Symlink-Target-Etag header value which may be None
    :raise: HTTPPreconditionFailed if x-symlink-target value
            is not well formatted.
    :raise: HTTPBadRequest if the x-symlink-target value points to the request
            path.
    :raise: HTTPBadRequest if the x-symlink-target-etag value contains
            a semicolon, double-quote, or backslash.
    """
    # N.B. check_path_header doesn't assert the leading slash and
    # copy middleware may accept the format. In the symlink, API
    # says apparently to use "container/object" format so add the
    # validation first, here.
    error_body = 'X-Symlink-Target header must be of the form ' \
                 '<container name>/<object name>'
    if wsgi_unquote(req.headers[TGT_OBJ_SYMLINK_HDR]).startswith('/'):
        raise HTTPPreconditionFailed(body=error_body,
                                     request=req,
                                     content_type='text/plain')

    # check container and object format
    container, obj = check_path_header(req, TGT_OBJ_SYMLINK_HDR, 2, error_body)
    req.headers[TGT_OBJ_SYMLINK_HDR] = wsgi_quote('%s/%s' % (container, obj))

    # Check account format if it exists
    account = check_account_format(
        req, wsgi_unquote(req.headers[TGT_ACCT_SYMLINK_HDR])) \
        if TGT_ACCT_SYMLINK_HDR in req.headers else None

    # Extract request path
    _junk, req_acc, req_cont, req_obj = req.split_path(4, 4, True)

    if account:
        req.headers[TGT_ACCT_SYMLINK_HDR] = wsgi_quote(account)
    else:
        account = req_acc

    # Check if symlink targets the symlink itself or not
    if (account, container, obj) == (req_acc, req_cont, req_obj):
        raise HTTPBadRequest(body='Symlink cannot target itself',
                             request=req,
                             content_type='text/plain')
    etag = req.headers.get(TGT_ETAG_SYMLINK_HDR, None)
    if etag and any(c in etag for c in ';"\\'):
        # See cgi.parse_header for why the above chars are problematic
        raise HTTPBadRequest(body='Bad %s format' %
                             TGT_ETAG_SYMLINK_HDR.title(),
                             request=req,
                             content_type='text/plain')
    if not (etag or req.headers.get('Content-Type')):
        req.headers['Content-Type'] = 'application/symlink'
    return '/v1/%s/%s/%s' % (account, container, obj), etag
Exemplo n.º 2
0
def _check_symlink_header(req):
    """
    Validate that the value from x-symlink-target header is
    well formatted. We assume the caller ensures that
    x-symlink-target header is present in req.headers.

    :param req: HTTP request object
    :raise: HTTPPreconditionFailed if x-symlink-target value
            is not well formatted.
    :raise: HTTPBadRequest if the x-symlink-target value points to the request
            path.
    """
    # N.B. check_path_header doesn't assert the leading slash and
    # copy middleware may accept the format. In the symlink, API
    # says apparently to use "container/object" format so add the
    # validation first, here.
    error_body = 'X-Symlink-Target header must be of the form ' \
                 '<container name>/<object name>'
    try:
        if wsgi_unquote(req.headers[TGT_OBJ_SYMLINK_HDR]).startswith('/'):
            raise HTTPPreconditionFailed(body=error_body,
                                         request=req,
                                         content_type='text/plain')
    except TypeError:
        raise HTTPPreconditionFailed(body=error_body,
                                     request=req,
                                     content_type='text/plain')

    # check container and object format
    container, obj = check_path_header(req, TGT_OBJ_SYMLINK_HDR, 2, error_body)
    req.headers[TGT_OBJ_SYMLINK_HDR] = wsgi_quote('%s/%s' % (container, obj))

    # Check account format if it exists
    try:
        account = check_account_format(
            req, wsgi_unquote(req.headers[TGT_ACCT_SYMLINK_HDR])) \
            if TGT_ACCT_SYMLINK_HDR in req.headers else None
    except TypeError:
        raise HTTPPreconditionFailed(
            body='Account name cannot contain slashes',
            request=req,
            content_type='text/plain')

    # Extract request path
    _junk, req_acc, req_cont, req_obj = req.split_path(4, 4, True)

    if account:
        req.headers[TGT_ACCT_SYMLINK_HDR] = wsgi_quote(account)
    else:
        account = req_acc

    # Check if symlink targets the symlink itself or not
    if (account, container, obj) == (req_acc, req_cont, req_obj):
        raise HTTPBadRequest(body='Symlink cannot target itself',
                             request=req,
                             content_type='text/plain')
Exemplo n.º 3
0
 def handle_COPY(self, req, start_response, account, container, obj):
     if not req.headers.get('Destination'):
         return HTTPPreconditionFailed(request=req,
                                       body='Destination header required')(
                                           req.environ, start_response)
     dest_account = account
     if 'Destination-Account' in req.headers:
         dest_account = wsgi_unquote(req.headers.get('Destination-Account'))
         dest_account = check_account_format(req, dest_account)
         req.headers['X-Copy-From-Account'] = wsgi_quote(account)
         account = dest_account
         del req.headers['Destination-Account']
     dest_container, dest_object = _check_destination_header(req)
     source = '/%s/%s' % (container, obj)
     container = dest_container
     obj = dest_object
     # re-write the existing request as a PUT instead of creating a new one
     req.method = 'PUT'
     # As this the path info is updated with destination container,
     # the proxy server app will use the right object controller
     # implementation corresponding to the container's policy type.
     ver, _junk = req.split_path(1, 2, rest_with_last=True)
     req.path_info = '/%s/%s/%s/%s' % (ver, dest_account, dest_container,
                                       dest_object)
     req.headers['Content-Length'] = 0
     req.headers['X-Copy-From'] = wsgi_quote(source)
     del req.headers['Destination']
     return self.handle_PUT(req, start_response)
Exemplo n.º 4
0
    def handle_request(self, req, start_response):
        """
        Take a GET or HEAD request, and if it is for a dynamic large object
        manifest, return an appropriate response.

        Otherwise, simply pass it through.
        """
        update_ignore_range_header(req, 'X-Object-Manifest')
        resp_iter = self._app_call(req.environ)

        # make sure this response is for a dynamic large object manifest
        for header, value in self._response_headers:
            if (header.lower() == 'x-object-manifest'):
                content_length = self._response_header_value('content-length')
                if content_length is not None and int(content_length) < 1024:
                    # Go ahead and consume small bodies
                    drain_and_close(resp_iter)
                close_if_possible(resp_iter)
                response = self.get_or_head_response(
                    req, wsgi_to_str(wsgi_unquote(value)))
                return response(req.environ, start_response)
        # Not a dynamic large object manifest; just pass it through.
        start_response(self._response_status,
                       self._response_headers,
                       self._response_exc_info)
        return resp_iter
Exemplo n.º 5
0
Arquivo: copy.py Projeto: mahak/swift
 def handle_COPY(self, req, start_response, account, container, obj):
     if not req.headers.get('Destination'):
         return HTTPPreconditionFailed(request=req,
                                       body='Destination header required'
                                       )(req.environ, start_response)
     dest_account = account
     if 'Destination-Account' in req.headers:
         dest_account = wsgi_unquote(req.headers.get('Destination-Account'))
         dest_account = check_account_format(req, dest_account)
         req.headers['X-Copy-From-Account'] = wsgi_quote(account)
         account = dest_account
         del req.headers['Destination-Account']
     dest_container, dest_object = _check_destination_header(req)
     source = '/%s/%s' % (container, obj)
     container = dest_container
     obj = dest_object
     # re-write the existing request as a PUT instead of creating a new one
     req.method = 'PUT'
     # As this the path info is updated with destination container,
     # the proxy server app will use the right object controller
     # implementation corresponding to the container's policy type.
     ver, _junk = req.split_path(1, 2, rest_with_last=True)
     req.path_info = '/%s/%s/%s/%s' % (
         ver, dest_account, dest_container, dest_object)
     req.headers['Content-Length'] = 0
     req.headers['X-Copy-From'] = wsgi_quote(source)
     del req.headers['Destination']
     return self.handle_PUT(req, start_response)
Exemplo n.º 6
0
Arquivo: wsgi.py Projeto: mahak/swift
 def parse_request(self):
     if not six.PY2:
         # request lines *should* be ascii per the RFC, but historically
         # we've allowed (and even have func tests that use) arbitrary
         # bytes. This breaks on py3 (see https://bugs.python.org/issue33973
         # ) but the work-around is simple: munge the request line to be
         # properly quoted. py2 will do the right thing without this, but it
         # doesn't hurt to re-write the request line like this and it
         # simplifies testing.
         if self.raw_requestline.count(b' ') >= 2:
             parts = self.raw_requestline.split(b' ', 2)
             path, q, query = parts[1].partition(b'?')
             # unquote first, so we don't over-quote something
             # that was *correctly* quoted
             path = wsgi_to_bytes(wsgi_quote(wsgi_unquote(
                 bytes_to_wsgi(path))))
             query = b'&'.join(
                 sep.join([
                     wsgi_to_bytes(wsgi_quote_plus(wsgi_unquote_plus(
                         bytes_to_wsgi(key)))),
                     wsgi_to_bytes(wsgi_quote_plus(wsgi_unquote_plus(
                         bytes_to_wsgi(val))))
                 ])
                 for part in query.split(b'&')
                 for key, sep, val in (part.partition(b'='), ))
             parts[1] = path + q + query
             self.raw_requestline = b' '.join(parts)
         # else, mangled protocol, most likely; let base class deal with it
     return wsgi.HttpProtocol.parse_request(self)
Exemplo n.º 7
0
Arquivo: bulk.py Projeto: mahak/swift
    def get_objs_to_delete(self, req):
        """
        Will populate objs_to_delete with data from request input.
        :params req: a Swob request
        :returns: a list of the contents of req.body when separated by newline.
        :raises HTTPException: on failures
        """
        line = b''
        data_remaining = True
        objs_to_delete = []
        if req.content_length is None and \
                req.headers.get('transfer-encoding', '').lower() != 'chunked':
            raise HTTPLengthRequired(request=req)

        while data_remaining:
            if b'\n' in line:
                obj_to_delete, line = line.split(b'\n', 1)
                if six.PY2:
                    obj_to_delete = wsgi_unquote(obj_to_delete.strip())
                else:
                    # yeah, all this chaining is pretty terrible...
                    # but it gets even worse trying to use UTF-8 and
                    # errors='surrogateescape' when dealing with terrible
                    # input like b'\xe2%98\x83'
                    obj_to_delete = wsgi_to_str(wsgi_unquote(
                        bytes_to_wsgi(obj_to_delete.strip())))
                objs_to_delete.append({'name': obj_to_delete})
            else:
                data = req.body_file.read(self.max_path_length)
                if data:
                    line += data
                else:
                    data_remaining = False
                    if six.PY2:
                        obj_to_delete = wsgi_unquote(line.strip())
                    else:
                        obj_to_delete = wsgi_to_str(wsgi_unquote(
                            bytes_to_wsgi(line.strip())))
                    if obj_to_delete:
                        objs_to_delete.append({'name': obj_to_delete})
            if len(objs_to_delete) > self.max_deletes_per_request:
                raise HTTPRequestEntityTooLarge(
                    'Maximum Bulk Deletes: %d per request' %
                    self.max_deletes_per_request)
            if len(line) > self.max_path_length * 2:
                raise HTTPBadRequest('Invalid File Name')
        return objs_to_delete
Exemplo n.º 8
0
    def get_objs_to_delete(self, req):
        """
        Will populate objs_to_delete with data from request input.
        :params req: a Swob request
        :returns: a list of the contents of req.body when separated by newline.
        :raises HTTPException: on failures
        """
        line = b''
        data_remaining = True
        objs_to_delete = []
        if req.content_length is None and \
                req.headers.get('transfer-encoding', '').lower() != 'chunked':
            raise HTTPLengthRequired(request=req)

        while data_remaining:
            if b'\n' in line:
                obj_to_delete, line = line.split(b'\n', 1)
                if six.PY2:
                    obj_to_delete = wsgi_unquote(obj_to_delete.strip())
                else:
                    # yeah, all this chaining is pretty terrible...
                    # but it gets even worse trying to use UTF-8 and
                    # errors='surrogateescape' when dealing with terrible
                    # input like b'\xe2%98\x83'
                    obj_to_delete = wsgi_to_str(
                        wsgi_unquote(bytes_to_wsgi(obj_to_delete.strip())))
                objs_to_delete.append({'name': obj_to_delete})
            else:
                data = req.body_file.read(self.max_path_length)
                if data:
                    line += data
                else:
                    data_remaining = False
                    if six.PY2:
                        obj_to_delete = wsgi_unquote(line.strip())
                    else:
                        obj_to_delete = wsgi_to_str(
                            wsgi_unquote(bytes_to_wsgi(line.strip())))
                    if obj_to_delete:
                        objs_to_delete.append({'name': obj_to_delete})
            if len(objs_to_delete) > self.max_deletes_per_request:
                raise HTTPRequestEntityTooLarge(
                    'Maximum Bulk Deletes: %d per request' %
                    self.max_deletes_per_request)
            if len(line) > self.max_path_length * 2:
                raise HTTPBadRequest('Invalid File Name')
        return objs_to_delete
Exemplo n.º 9
0
    def object_request(self, req, api_version, account, container, obj,
                       allow_versioned_writes):
        """
        Handle request for object resource.

        Note that account, container, obj should be unquoted by caller
        if the url path is under url encoding (e.g. %FF)

        :param req: swift.common.swob.Request instance
        :param api_version: should be v1 unless swift bumps api version
        :param account: account name string
        :param container: container name string
        :param object: object name string
        """
        resp = None
        is_enabled = config_true_value(allow_versioned_writes)
        container_info = get_container_info(
            req.environ, self.app)

        # To maintain backwards compatibility, container version
        # location could be stored as sysmeta or not, need to check both.
        # If stored as sysmeta, check if middleware is enabled. If sysmeta
        # is not set, but versions property is set in container_info, then
        # for backwards compatibility feature is enabled.
        versions_cont = container_info.get(
            'sysmeta', {}).get('versions-location')
        versioning_mode = container_info.get(
            'sysmeta', {}).get('versions-mode', 'stack')
        if not versions_cont:
            versions_cont = container_info.get('versions')
            # if allow_versioned_writes is not set in the configuration files
            # but 'versions' is configured, enable feature to maintain
            # backwards compatibility
            if not allow_versioned_writes and versions_cont:
                is_enabled = True

        if is_enabled and versions_cont:
            versions_cont = wsgi_unquote(str_to_wsgi(
                versions_cont)).split('/')[0]
            vw_ctx = VersionedWritesContext(self.app, self.logger)
            if req.method == 'PUT':
                resp = vw_ctx.handle_obj_versions_put(
                    req, versions_cont, api_version, account,
                    obj)
            # handle DELETE
            elif versioning_mode == 'history':
                resp = vw_ctx.handle_obj_versions_delete_push(
                    req, versions_cont, api_version, account,
                    container, obj)
            else:
                resp = vw_ctx.handle_obj_versions_delete_pop(
                    req, versions_cont, api_version, account,
                    container, obj)

        if resp:
            return resp
        else:
            return self.app
Exemplo n.º 10
0
    def object_request(self, req, api_version, account, container, obj,
                       allow_versioned_writes):
        """
        Handle request for object resource.

        Note that account, container, obj should be unquoted by caller
        if the url path is under url encoding (e.g. %FF)

        :param req: swift.common.swob.Request instance
        :param api_version: should be v1 unless swift bumps api version
        :param account: account name string
        :param container: container name string
        :param object: object name string
        """
        resp = None
        is_enabled = config_true_value(allow_versioned_writes)
        container_info = get_container_info(
            req.environ, self.app, swift_source='VW')

        # To maintain backwards compatibility, container version
        # location could be stored as sysmeta or not, need to check both.
        # If stored as sysmeta, check if middleware is enabled. If sysmeta
        # is not set, but versions property is set in container_info, then
        # for backwards compatibility feature is enabled.
        versions_cont = container_info.get(
            'sysmeta', {}).get('versions-location')
        versioning_mode = container_info.get(
            'sysmeta', {}).get('versions-mode', 'stack')
        if not versions_cont:
            versions_cont = container_info.get('versions')
            # if allow_versioned_writes is not set in the configuration files
            # but 'versions' is configured, enable feature to maintain
            # backwards compatibility
            if not allow_versioned_writes and versions_cont:
                is_enabled = True

        if is_enabled and versions_cont:
            versions_cont = wsgi_unquote(str_to_wsgi(
                versions_cont)).split('/')[0]
            vw_ctx = VersionedWritesContext(self.app, self.logger)
            if req.method == 'PUT':
                resp = vw_ctx.handle_obj_versions_put(
                    req, versions_cont, api_version, account,
                    obj)
            # handle DELETE
            elif versioning_mode == 'history':
                resp = vw_ctx.handle_obj_versions_delete_push(
                    req, versions_cont, api_version, account,
                    container, obj)
            else:
                resp = vw_ctx.handle_obj_versions_delete_pop(
                    req, versions_cont, api_version, account,
                    container, obj)

        if resp:
            return resp
        else:
            return self.app
Exemplo n.º 11
0
    def handle_request(self, req, versions_cont, api_version, account,
                       container, obj, is_enabled):
        if req.method == 'PUT':
            return self.handle_put(req, versions_cont, api_version, account,
                                   obj, is_enabled)
        elif req.method == 'POST':
            return self.handle_post(req, versions_cont, account)
        elif req.method == 'DELETE':
            return self.handle_delete(req, versions_cont, api_version, account,
                                      container, obj, is_enabled)

        # GET/HEAD/OPTIONS
        resp = req.get_response(self.app)

        resp.headers['X-Object-Version-Id'] = resp.headers[VERSION_ID_HEADER]
        # Check for a "real" version
        loc = wsgi_unquote(resp.headers.get('Content-Location', ''))
        if loc:
            _, acct, cont, version_obj = split_path(loc, 4, 4, True)
            if acct == account and cont == versions_cont:
                _, version = self._split_version_from_name(version_obj)
                if version is not None:
                    resp.headers['X-Object-Version-Id'] = version.internal
                    content_loc = wsgi_quote('/%s/%s/%s/%s' % (
                        api_version,
                        account,
                        container,
                        obj,
                    )) + '?version-id=%s' % (version.internal, )
                    resp.headers['Content-Location'] = content_loc
        symlink_target = wsgi_unquote(resp.headers.get('X-Symlink-Target', ''))
        if symlink_target:
            cont, version_obj = split_path('/%s' % symlink_target, 2, 2, True)
            if cont == versions_cont:
                _, version = self._split_version_from_name(version_obj)
                if version is not None:
                    resp.headers['X-Object-Version-Id'] = version.internal
                    symlink_target = wsgi_quote('%s/%s' % (container, obj)) + \
                        '?version-id=%s' % (version.internal,)
                    resp.headers['X-Symlink-Target'] = symlink_target
        return resp
Exemplo n.º 12
0
def make_subrequest(env,
                    method=None,
                    path=None,
                    body=None,
                    headers=None,
                    agent='Swift',
                    swift_source=None,
                    make_env=make_env):
    """
    Makes a new swob.Request based on the current env but with the
    parameters specified.

    :param env: The WSGI environment to base the new request on.
    :param method: HTTP method of new request; default is from
                   the original env.
    :param path: HTTP path of new request; default is from the
                 original env. path should be compatible with what you
                 would send to Request.blank. path should be quoted and it
                 can include a query string. for example:
                 '/a%20space?unicode_str%E8%AA%9E=y%20es'
    :param body: HTTP body of new request; empty by default.
    :param headers: Extra HTTP headers of new request; None by
                    default.
    :param agent: The HTTP user agent to use; default 'Swift'. You
                  can put %(orig)s in the agent to have it replaced
                  with the original env's HTTP_USER_AGENT, such as
                  '%(orig)s StaticWeb'. You also set agent to None to
                  use the original env's HTTP_USER_AGENT or '' to
                  have no HTTP_USER_AGENT.
    :param swift_source: Used to mark the request as originating out of
                         middleware. Will be logged in proxy logs.
    :param make_env: make_subrequest calls this make_env to help build the
        swob.Request.
    :returns: Fresh swob.Request object.
    """
    query_string = None
    path = path or ''
    if path and '?' in path:
        path, query_string = path.split('?', 1)
    newenv = make_env(env,
                      method,
                      path=wsgi_unquote(path),
                      agent=agent,
                      query_string=query_string,
                      swift_source=swift_source)
    if not headers:
        headers = {}
    if body:
        return Request.blank(path, environ=newenv, body=body, headers=headers)
    else:
        return Request.blank(path, environ=newenv, headers=headers)
Exemplo n.º 13
0
    def object_request(self, req, api_version, account, container, obj):
        """
        Handle request for object resource.

        Note that account, container, obj should be unquoted by caller
        if the url path is under url encoding (e.g. %FF)

        :param req: swift.common.swob.Request instance
        :param api_version: should be v1 unless swift bumps api version
        :param account: account name string
        :param container: container name string
        :param object: object name string
        """
        resp = None
        container_info = get_container_info(req.environ,
                                            self.app,
                                            swift_source='OV')

        versions_cont = container_info.get('sysmeta',
                                           {}).get('versions-container', '')
        is_enabled = config_true_value(
            container_info.get('sysmeta', {}).get('versions-enabled'))

        if versions_cont:
            versions_cont = wsgi_unquote(
                str_to_wsgi(versions_cont)).split('/')[0]

        if req.params.get('version-id'):
            vw_ctx = OioObjectContext(self.app, self.logger)
            resp = vw_ctx.handle_versioned_request(req, versions_cont,
                                                   api_version, account,
                                                   container, obj, is_enabled,
                                                   req.params['version-id'])
        elif versions_cont:
            # handle object request for a enabled versioned container
            vw_ctx = OioObjectContext(self.app, self.logger)
            resp = vw_ctx.handle_request(req, versions_cont, api_version,
                                         account, container, obj, is_enabled)

        if resp:
            return resp
        else:
            return self.app
Exemplo n.º 14
0
    def handle_request(self, req, start_response):
        """
        Take a GET or HEAD request, and if it is for a dynamic large object
        manifest, return an appropriate response.

        Otherwise, simply pass it through.
        """
        resp_iter = self._app_call(req.environ)

        # make sure this response is for a dynamic large object manifest
        for header, value in self._response_headers:
            if (header.lower() == 'x-object-manifest'):
                close_if_possible(resp_iter)
                response = self.get_or_head_response(
                    req, wsgi_to_str(wsgi_unquote(value)))
                return response(req.environ, start_response)
        # Not a dynamic large object manifest; just pass it through.
        start_response(self._response_status, self._response_headers,
                       self._response_exc_info)
        return resp_iter
Exemplo n.º 15
0
Arquivo: wsgi.py Projeto: mahak/swift
def make_subrequest(env, method=None, path=None, body=None, headers=None,
                    agent='Swift', swift_source=None, make_env=make_env):
    """
    Makes a new swob.Request based on the current env but with the
    parameters specified.

    :param env: The WSGI environment to base the new request on.
    :param method: HTTP method of new request; default is from
                   the original env.
    :param path: HTTP path of new request; default is from the
                 original env. path should be compatible with what you
                 would send to Request.blank. path should be quoted and it
                 can include a query string. for example:
                 '/a%20space?unicode_str%E8%AA%9E=y%20es'
    :param body: HTTP body of new request; empty by default.
    :param headers: Extra HTTP headers of new request; None by
                    default.
    :param agent: The HTTP user agent to use; default 'Swift'. You
                  can put %(orig)s in the agent to have it replaced
                  with the original env's HTTP_USER_AGENT, such as
                  '%(orig)s StaticWeb'. You also set agent to None to
                  use the original env's HTTP_USER_AGENT or '' to
                  have no HTTP_USER_AGENT.
    :param swift_source: Used to mark the request as originating out of
                         middleware. Will be logged in proxy logs.
    :param make_env: make_subrequest calls this make_env to help build the
        swob.Request.
    :returns: Fresh swob.Request object.
    """
    query_string = None
    path = path or ''
    if path and '?' in path:
        path, query_string = path.split('?', 1)
    newenv = make_env(env, method, path=wsgi_unquote(path), agent=agent,
                      query_string=query_string, swift_source=swift_source)
    if not headers:
        headers = {}
    if body:
        return Request.blank(path, environ=newenv, body=body, headers=headers)
    else:
        return Request.blank(path, environ=newenv, headers=headers)
Exemplo n.º 16
0
def check_path_header(req, name, length, error_msg):
    """
    Validate that the value of path-like header is
    well formatted. We assume the caller ensures that
    specific header is present in req.headers.

    :param req: HTTP request object
    :param name: header name
    :param length: length of path segment check
    :param error_msg: error message for client
    :returns: A tuple with path parts according to length
    :raise: HTTPPreconditionFailed if header value
            is not well formatted.
    """
    hdr = wsgi_unquote(req.headers.get(name))
    if not hdr.startswith('/'):
        hdr = '/' + hdr
    try:
        return split_path(hdr, length, length, True)
    except ValueError:
        raise HTTPPreconditionFailed(request=req, body=error_msg)
Exemplo n.º 17
0
Arquivo: dlo.py Projeto: mahak/swift
    def handle_request(self, req, start_response):
        """
        Take a GET or HEAD request, and if it is for a dynamic large object
        manifest, return an appropriate response.

        Otherwise, simply pass it through.
        """
        resp_iter = self._app_call(req.environ)

        # make sure this response is for a dynamic large object manifest
        for header, value in self._response_headers:
            if (header.lower() == 'x-object-manifest'):
                close_if_possible(resp_iter)
                response = self.get_or_head_response(
                    req, wsgi_to_str(wsgi_unquote(value)))
                return response(req.environ, start_response)
        # Not a dynamic large object manifest; just pass it through.
        start_response(self._response_status,
                       self._response_headers,
                       self._response_exc_info)
        return resp_iter
Exemplo n.º 18
0
def check_path_header(req, name, length, error_msg):
    """
    Validate that the value of path-like header is
    well formatted. We assume the caller ensures that
    specific header is present in req.headers.

    :param req: HTTP request object
    :param name: header name
    :param length: length of path segment check
    :param error_msg: error message for client
    :returns: A tuple with path parts according to length
    :raise: HTTPPreconditionFailed if header value
            is not well formatted.
    """
    hdr = wsgi_unquote(req.headers.get(name))
    if not hdr.startswith('/'):
        hdr = '/' + hdr
    try:
        return split_path(hdr, length, length, True)
    except ValueError:
        raise HTTPPreconditionFailed(
            request=req,
            body=error_msg)
Exemplo n.º 19
0
Arquivo: copy.py Projeto: mahak/swift
    def handle_PUT(self, req, start_response):
        if req.content_length:
            return HTTPBadRequest(body='Copy requests require a zero byte '
                                  'body', request=req,
                                  content_type='text/plain')(req.environ,
                                                             start_response)

        # Form the path of source object to be fetched
        ver, acct, _rest = req.split_path(2, 3, True)
        src_account_name = req.headers.get('X-Copy-From-Account')
        if src_account_name:
            src_account_name = check_account_format(
                req, wsgi_unquote(src_account_name))
        else:
            src_account_name = acct
        src_container_name, src_obj_name = _check_copy_from_header(req)
        source_path = '/%s/%s/%s/%s' % (ver, src_account_name,
                                        src_container_name, src_obj_name)

        # GET the source object, bail out on error
        ssc_ctx = ServerSideCopyWebContext(self.app, self.logger)
        source_resp = self._get_source_object(ssc_ctx, source_path, req)
        if source_resp.status_int >= HTTP_MULTIPLE_CHOICES:
            return source_resp(source_resp.environ, start_response)

        # Create a new Request object based on the original request instance.
        # This will preserve original request environ including headers.
        sink_req = Request.blank(req.path_info, environ=req.environ)

        def is_object_sysmeta(k):
            return is_sys_meta('object', k)

        if config_true_value(req.headers.get('x-fresh-metadata', 'false')):
            # x-fresh-metadata only applies to copy, not post-as-copy: ignore
            # existing user metadata, update existing sysmeta with new
            copy_header_subset(source_resp, sink_req, is_object_sysmeta)
            copy_header_subset(req, sink_req, is_object_sysmeta)
        else:
            # First copy existing sysmeta, user meta and other headers from the
            # source to the sink, apart from headers that are conditionally
            # copied below and timestamps.
            exclude_headers = ('x-static-large-object', 'x-object-manifest',
                               'etag', 'content-type', 'x-timestamp',
                               'x-backend-timestamp')
            copy_header_subset(source_resp, sink_req,
                               lambda k: k.lower() not in exclude_headers)
            # now update with original req headers
            sink_req.headers.update(req.headers)

        params = sink_req.params
        if params.get('multipart-manifest') == 'get':
            if 'X-Static-Large-Object' in source_resp.headers:
                params['multipart-manifest'] = 'put'
            if 'X-Object-Manifest' in source_resp.headers:
                del params['multipart-manifest']
                sink_req.headers['X-Object-Manifest'] = \
                    source_resp.headers['X-Object-Manifest']
            sink_req.params = params

        # Set swift.source, data source, content length and etag
        # for the PUT request
        sink_req.environ['swift.source'] = 'SSC'
        sink_req.environ['wsgi.input'] = FileLikeIter(source_resp.app_iter)
        sink_req.content_length = source_resp.content_length
        if (source_resp.status_int == HTTP_OK and
                'X-Static-Large-Object' not in source_resp.headers and
                ('X-Object-Manifest' not in source_resp.headers or
                 req.params.get('multipart-manifest') == 'get')):
            # copy source etag so that copied content is verified, unless:
            #  - not a 200 OK response: source etag may not match the actual
            #    content, for example with a 206 Partial Content response to a
            #    ranged request
            #  - SLO manifest: etag cannot be specified in manifest PUT; SLO
            #    generates its own etag value which may differ from source
            #  - SLO: etag in SLO response is not hash of actual content
            #  - DLO: etag in DLO response is not hash of actual content
            sink_req.headers['Etag'] = source_resp.etag
        else:
            # since we're not copying the source etag, make sure that any
            # container update override values are not copied.
            remove_items(sink_req.headers, lambda k: k.startswith(
                'X-Object-Sysmeta-Container-Update-Override-'))

        # We no longer need these headers
        sink_req.headers.pop('X-Copy-From', None)
        sink_req.headers.pop('X-Copy-From-Account', None)

        # If the copy request does not explicitly override content-type,
        # use the one present in the source object.
        if not req.headers.get('content-type'):
            sink_req.headers['Content-Type'] = \
                source_resp.headers['Content-Type']

        # Create response headers for PUT response
        resp_headers = self._create_response_headers(source_path,
                                                     source_resp, sink_req)

        put_resp = ssc_ctx.send_put_req(sink_req, resp_headers, start_response)
        close_if_possible(source_resp.app_iter)
        return put_resp
Exemplo n.º 20
0
    def handle_PUT(self, req, start_response):
        if req.content_length:
            return HTTPBadRequest(body='Copy requests require a zero byte '
                                  'body',
                                  request=req,
                                  content_type='text/plain')(req.environ,
                                                             start_response)

        # Form the path of source object to be fetched
        ver, acct, _rest = req.split_path(2, 3, True)
        src_account_name = req.headers.get('X-Copy-From-Account')
        if src_account_name:
            src_account_name = check_account_format(
                req, wsgi_unquote(src_account_name))
        else:
            src_account_name = acct
        src_container_name, src_obj_name = _check_copy_from_header(req)
        source_path = '/%s/%s/%s/%s' % (ver, src_account_name,
                                        src_container_name, src_obj_name)

        # GET the source object, bail out on error
        ssc_ctx = ServerSideCopyWebContext(self.app, self.logger)
        source_resp = self._get_source_object(ssc_ctx, source_path, req)
        if source_resp.status_int >= HTTP_MULTIPLE_CHOICES:
            return source_resp(source_resp.environ, start_response)

        # Create a new Request object based on the original request instance.
        # This will preserve original request environ including headers.
        sink_req = Request.blank(req.path_info, environ=req.environ)

        def is_object_sysmeta(k):
            return is_sys_meta('object', k)

        if config_true_value(req.headers.get('x-fresh-metadata', 'false')):
            # x-fresh-metadata only applies to copy, not post-as-copy: ignore
            # existing user metadata, update existing sysmeta with new
            copy_header_subset(source_resp, sink_req, is_object_sysmeta)
            copy_header_subset(req, sink_req, is_object_sysmeta)
        else:
            # First copy existing sysmeta, user meta and other headers from the
            # source to the sink, apart from headers that are conditionally
            # copied below and timestamps.
            exclude_headers = ('x-static-large-object', 'x-object-manifest',
                               'etag', 'content-type', 'x-timestamp',
                               'x-backend-timestamp')
            copy_header_subset(source_resp, sink_req,
                               lambda k: k.lower() not in exclude_headers)
            # now update with original req headers
            sink_req.headers.update(req.headers)

        params = sink_req.params
        params_updated = False

        if params.get('multipart-manifest') == 'get':
            if 'X-Static-Large-Object' in source_resp.headers:
                params['multipart-manifest'] = 'put'
            if 'X-Object-Manifest' in source_resp.headers:
                del params['multipart-manifest']
                sink_req.headers['X-Object-Manifest'] = \
                    source_resp.headers['X-Object-Manifest']
            params_updated = True

        if 'version-id' in params:
            del params['version-id']
            params_updated = True

        if params_updated:
            sink_req.params = params

        # Set swift.source, data source, content length and etag
        # for the PUT request
        sink_req.environ['swift.source'] = 'SSC'
        sink_req.environ['wsgi.input'] = FileLikeIter(source_resp.app_iter)
        sink_req.content_length = source_resp.content_length
        if (source_resp.status_int == HTTP_OK
                and 'X-Static-Large-Object' not in source_resp.headers
                and ('X-Object-Manifest' not in source_resp.headers
                     or req.params.get('multipart-manifest') == 'get')):
            # copy source etag so that copied content is verified, unless:
            #  - not a 200 OK response: source etag may not match the actual
            #    content, for example with a 206 Partial Content response to a
            #    ranged request
            #  - SLO manifest: etag cannot be specified in manifest PUT; SLO
            #    generates its own etag value which may differ from source
            #  - SLO: etag in SLO response is not hash of actual content
            #  - DLO: etag in DLO response is not hash of actual content
            sink_req.headers['Etag'] = source_resp.etag
        else:
            # since we're not copying the source etag, make sure that any
            # container update override values are not copied.
            remove_items(
                sink_req.headers, lambda k: k.startswith(
                    OBJECT_SYSMETA_CONTAINER_UPDATE_OVERRIDE_PREFIX.title()))

        # We no longer need these headers
        sink_req.headers.pop('X-Copy-From', None)
        sink_req.headers.pop('X-Copy-From-Account', None)

        # If the copy request does not explicitly override content-type,
        # use the one present in the source object.
        if not req.headers.get('content-type'):
            sink_req.headers['Content-Type'] = \
                source_resp.headers['Content-Type']

        # Create response headers for PUT response
        resp_headers = self._create_response_headers(source_path, source_resp,
                                                     sink_req)

        put_resp = ssc_ctx.send_put_req(sink_req, resp_headers, start_response)
        close_if_possible(source_resp.app_iter)
        return put_resp
Exemplo n.º 21
0
    def handle_versioned_request(self, req, versions_cont, api_version,
                                 account, container, obj, is_enabled, version):
        """
        Handle 'version-id' request for object resource. When a request
        contains a ``version-id=<id>`` parameter, the request is acted upon
        the actual version of that object. Version-aware operations
        require that the container is versioned, but do not require that
        the versioning is currently enabled. Users should be able to
        operate on older versions of an object even if versioning is
        currently suspended.

        PUT and POST requests are not allowed as that would overwrite
        the contents of the versioned object.

        :param req: The original request
        :param versions_cont: container holding versions of the requested obj
        :param api_version: should be v1 unless swift bumps api version
        :param account: account name string
        :param container: container name string
        :param object: object name string
        :param is_enabled: is versioning currently enabled
        :param version: version of the object to act on
        """
        if not versions_cont and version != 'null':
            raise HTTPBadRequest(
                'version-aware operations require that the container is '
                'versioned',
                request=req)
        req.environ['oio.query'] = {'version': version}
        if version != 'null':
            try:
                int(version)
            except ValueError:
                raise HTTPBadRequest('Invalid version parameter', request=req)

        if req.method == 'DELETE':
            return self.handle_delete_version(req, versions_cont, api_version,
                                              account, container, obj,
                                              is_enabled, version)
        elif req.method == 'PUT':
            return self.handle_put_version(req, versions_cont, api_version,
                                           account, container, obj, is_enabled,
                                           version)
        if version == 'null':
            resp = req.get_response(self.app)
            if resp.is_success:
                if get_reserved_name('versions', '') in wsgi_unquote(
                        resp.headers.get('Content-Location', '')):
                    # Have a latest version, but it's got a real version-id.
                    # Since the user specifically asked for null, return 404
                    close_if_possible(resp.app_iter)
                    raise HTTPNotFound(request=req)
                resp.headers['X-Object-Version-Id'] = 'null'
                if req.method == 'HEAD':
                    drain_and_close(resp)
            return resp
        else:
            resp = req.get_response(self.app)
            if resp.is_success:
                resp.headers['X-Object-Version-Id'] = version

            # Well, except for some delete marker business...
            is_del_marker = DELETE_MARKER_CONTENT_TYPE == resp.headers.get(
                'X-Backend-Content-Type', resp.headers['Content-Type'])

            if req.method == 'HEAD':
                drain_and_close(resp)

            if is_del_marker:
                hdrs = {
                    'X-Object-Version-Id': version,
                    'Content-Type': DELETE_MARKER_CONTENT_TYPE
                }
                raise HTTPNotFound(request=req, headers=hdrs)
            return resp
Exemplo n.º 22
0
    def handle_request(self, req, start_response):
        """
        Handle request for container resource.

        On PUT, POST set version location and enabled flag sysmeta.
        For container listings of a versioned container, update the object's
        bytes and etag to use the target's instead of using the symlink info.
        """
        # FIXME(FVE): this request is not needed when handling
        # list-object-versions requests.
        app_resp = self._app_call(req.environ)
        _, account, container, _ = req.split_path(3, 4, True)
        location = ''
        curr_bytes = 0
        bytes_idx = -1
        for i, (header, value) in enumerate(self._response_headers):
            if header == 'X-Container-Bytes-Used':
                curr_bytes = value
                bytes_idx = i
            if header.lower() == SYSMETA_VERSIONS_CONT:
                location = value
            if header.lower() == SYSMETA_VERSIONS_ENABLED:
                self._response_headers.extend([
                    (CLIENT_VERSIONS_ENABLED.title(), value)
                ])

        if location:
            location = wsgi_unquote(location)

            # update bytes header
            if bytes_idx > -1:
                head_req = make_pre_authed_request(
                    req.environ,
                    method='HEAD',
                    swift_source='OV',
                    path=wsgi_quote('/v1/%s/%s' % (account, location)),
                    headers={'X-Backend-Allow-Reserved-Names': 'true'})
                vresp = head_req.get_response(self.app)
                if vresp.is_success:
                    ver_bytes = vresp.headers.get('X-Container-Bytes-Used', 0)
                    self._response_headers[bytes_idx] = (
                        'X-Container-Bytes-Used',
                        str(int(curr_bytes) + int(ver_bytes)))
                drain_and_close(vresp)
        elif is_success(self._get_status_int()):
            # If client is doing a version-aware listing for a container that
            # (as best we could tell) has never had versioning enabled,
            # err on the side of there being data anyway -- the metadata we
            # found may not be the most up-to-date.

            # Note that any extra listing request we make will likely 404.
            try:
                location = self._build_versions_container_name(container)
            except ValueError:
                # may be internal listing to a reserved namespace container
                pass
        # else, we won't need location anyway

        if req.method == 'GET' and 'versions' in req.params:
            return self._list_versions(req, start_response, container)

        start_response(self._response_status, self._response_headers,
                       self._response_exc_info)
        return app_resp