def _extract_path(self, path): account = self.account # Remove leading '/' to be consistent with split_path() obj = path[1:] container = None try: if self.strip_v1: version, tail = split_path('/' + obj, 1, 2, True) if version in ('v1', 'v1.0'): obj = tail if obj is not None and self.account_first: account, tail = split_path('/' + obj, 1, 2, True) obj = tail if obj is not None and self.swift3_compat: container, tail = split_path('/' + obj, 1, 2, True) obj = tail # Do not yield an empty object name if not obj: obj = None except ValueError: raise HTTPBadRequest() return account, container, obj
def check_copy_source(self, app): """ check_copy_source checks the copy source existence and if copying an object to itself, for illegal request parameters """ if 'X-Amz-Copy-Source' in self.headers: src_path = unquote(self.headers['X-Amz-Copy-Source']) src_path = src_path if src_path.startswith('/') else \ ('/' + src_path) src_bucket, src_obj = split_path(src_path, 0, 2, True) headers = swob.HeaderKeyDict() headers.update(self._copy_source_headers()) src_resp = self.get_response(app, 'HEAD', src_bucket, src_obj, headers=headers) if src_resp.status_int == 304: # pylint: disable-msg=E1101 raise PreconditionFailed() self.headers['X-Amz-Copy-Source'] = \ '/' + self.headers['X-Amz-Copy-Source'].lstrip('/') source_container, source_obj = \ split_path(self.headers['X-Amz-Copy-Source'], 1, 2, True) if (self.container_name == source_container and self.object_name == source_obj): if self.headers.get('x-amz-metadata-directive', 'COPY') == 'COPY': raise InvalidRequest("This copy request is illegal " "because it is trying to copy an " "object to itself without " "changing the object's metadata, " "storage class, website redirect " "location or encryption " "attributes.")
def test_split_path(self): """ Test swift.common.utils.split_account_path """ self.assertRaises(ValueError, utils.split_path, '') self.assertRaises(ValueError, utils.split_path, '/') self.assertRaises(ValueError, utils.split_path, '//') self.assertEquals(utils.split_path('/a'), ['a']) self.assertRaises(ValueError, utils.split_path, '//a') self.assertEquals(utils.split_path('/a/'), ['a']) self.assertRaises(ValueError, utils.split_path, '/a/c') self.assertRaises(ValueError, utils.split_path, '//c') self.assertRaises(ValueError, utils.split_path, '/a/c/') self.assertRaises(ValueError, utils.split_path, '/a//') self.assertRaises(ValueError, utils.split_path, '/a', 2) self.assertRaises(ValueError, utils.split_path, '/a', 2, 3) self.assertRaises(ValueError, utils.split_path, '/a', 2, 3, True) self.assertEquals(utils.split_path('/a/c', 2), ['a', 'c']) self.assertEquals(utils.split_path('/a/c/o', 3), ['a', 'c', 'o']) self.assertRaises(ValueError, utils.split_path, '/a/c/o/r', 3, 3) self.assertEquals(utils.split_path('/a/c/o/r', 3, 3, True), ['a', 'c', 'o/r']) self.assertEquals(utils.split_path('/a/c', 2, 3, True), ['a', 'c', None]) self.assertRaises(ValueError, utils.split_path, '/a', 5, 4) self.assertEquals(utils.split_path('/a/c/', 2), ['a', 'c']) self.assertEquals(utils.split_path('/a/c/', 2, 3), ['a', 'c', '']) try: utils.split_path('o\nn e', 2) except ValueError, err: self.assertEquals(str(err), 'Invalid path: o%0An%20e')
def __call__(self, request): if request.method not in ("PUT","COPY"): return self.app try: split_path(request.path,2, 4, rest_with_last=True) except ValueError: return self.app new_quota = request.headers.get('X-Account-Meta-Quota-Bytes') if new_quota: if not new_quota.isdigit(): return jresponse('-1', 'bad request', request, 400) return self.app account_info = get_account_info(request.environ, self.app) new_size = int(account_info['bytes']) + (request.content_length or 0) quota = int(account_info['meta'].get('quota-bytes', -1)) if 0 <= quota < new_size: respbody='Your request is too large.' return jresponse('-1', respbody, request,413) return self.app
def GETorHEAD_base(self, req, server_type, ring, partition, path): """ Base handler for HTTP GET or HEAD requests. :param req: swob.Request object :param server_type: server type :param ring: the ring to obtain nodes from :param partition: partition :param path: path for the request :returns: swob.Response object """ backend_headers = self.generate_request_headers( req, additional=req.headers) handler = GetOrHeadHandler(self.app, req, server_type, ring, partition, path, backend_headers) res = handler.get_working_response(req) if not res: res = self.best_response( req, handler.statuses, handler.reasons, handler.bodies, '%s %s' % (server_type, req.method), headers=handler.source_headers) try: (account, container) = split_path(req.path_info, 1, 2) _set_info_cache(self.app, req.environ, account, container, res) except ValueError: pass try: (account, container, obj) = split_path(req.path_info, 3, 3, True) _set_object_info_cache(self.app, req.environ, account, container, obj, res) except ValueError: pass return res
def test_split_path(self): """ Test swift.common.utils.split_account_path """ self.assertRaises(ValueError, utils.split_path, "") self.assertRaises(ValueError, utils.split_path, "/") self.assertRaises(ValueError, utils.split_path, "//") self.assertEquals(utils.split_path("/a"), ["a"]) self.assertRaises(ValueError, utils.split_path, "//a") self.assertEquals(utils.split_path("/a/"), ["a"]) self.assertRaises(ValueError, utils.split_path, "/a/c") self.assertRaises(ValueError, utils.split_path, "//c") self.assertRaises(ValueError, utils.split_path, "/a/c/") self.assertRaises(ValueError, utils.split_path, "/a//") self.assertRaises(ValueError, utils.split_path, "/a", 2) self.assertRaises(ValueError, utils.split_path, "/a", 2, 3) self.assertRaises(ValueError, utils.split_path, "/a", 2, 3, True) self.assertEquals(utils.split_path("/a/c", 2), ["a", "c"]) self.assertEquals(utils.split_path("/a/c/o", 3), ["a", "c", "o"]) self.assertRaises(ValueError, utils.split_path, "/a/c/o/r", 3, 3) self.assertEquals(utils.split_path("/a/c/o/r", 3, 3, True), ["a", "c", "o/r"]) self.assertEquals(utils.split_path("/a/c", 2, 3, True), ["a", "c", None]) self.assertRaises(ValueError, utils.split_path, "/a", 5, 4) self.assertEquals(utils.split_path("/a/c/", 2), ["a", "c"]) self.assertEquals(utils.split_path("/a/c/", 2, 3), ["a", "c", ""]) try: utils.split_path("o\nn e", 2) except ValueError, err: self.assertEquals(str(err), "Invalid path: o%0An%20e")
def __call__(self, env, start_response): """ If called with header X-Pid-Create and Method PUT become active and create a PID and store it with the object :param env: request environment :param start_response: function that we call when creating response :return: """ self.start_response = start_response request = Request(env) if request.method == 'PUT': if 'X-Pid-Create' in list(request.headers.keys()): url = '{}{}'.format(request.host_url, request.path_info) if 'X-Pid-Parent' in list(request.headers.keys()): parent = request.headers['X-Pid-Parent'] else: parent = None success, pid = create_pid(object_url=url, api_url=self.conf.get('api_url'), username=self.conf.get('username'), password=self.conf.get('password'), parent=parent) if success: self.logger.info('Created a PID for {}'.format(url)) request.headers['X-Object-Meta-PID'] = pid response = PersistentIdentifierResponse( pid=pid, add_checksum=self.add_checksum, username=self.conf.get('username'), password=self.conf.get('password'), start_response=start_response, logger=self.logger) return self.app(env, response.finish_response) else: self.logger.error('Unable to create a PID for {},' 'because of {}'.format(url, pid)) return Response( status=502, body='Could not contact PID API')(env, start_response) elif request.method in ['GET', 'HEAD']: # only modify response if we have a request for a object try: split_path(request.path_info, 4, 4, True) except ValueError: return self.app(env, start_response) object_metadata = get_object_info( env=request.environ, app=self.app, swift_source='PersistentIdentifierMiddleware')['meta'] if 'pid' in object_metadata.keys(): response = PersistentIdentifierResponse( pid='', add_checksum='', username='', password='', start_response=start_response, logger=self.logger) return self.app(env, response.finish_response_pidurl) return self.app(env, start_response)
def __call__(self, env, start_response): """ Main hook into the WSGI paste.deploy filter/app pipeline. :param env: The WSGI environment dict. :param start_response: The WSGI start_response hook. """ try: (version, account, container, obj) = \ split_path(env['PATH_INFO'], 2, 4, True) except ValueError: return self.app(env, start_response) if not self._cache: self._cache = cache_from_env(env) # Don't handle non-GET requests. if env['REQUEST_METHOD'] not in ('HEAD', 'GET'): # flush cache if we expect the container metadata being changed. if container and not obj and self._cache: memcache_key = 'better_static/%s/%s' % (account, container) self._cache.delete(memcache_key) return self.app(env, start_response) # If non-html was explicitly requested, don't bother trying to format # the html params = urlparse.parse_qs(env.get('QUERY_STRING', '')) if 'format' in params and params['format'] != ['html']: return self.app(env, start_response) context = Context(self, env, account, container, obj) return context(env, start_response)
def handle_get_token(self, req): """ Handles the various `request for token and service end point(s)` calls. There are various formats to support the various auth servers in the past. Examples:: GET <auth-prefix>/v1/<act>/auth GET <auth-prefix>/auth GET <auth-prefix>/v1.0 All formats require GSS (Kerberos) authentication. On successful authentication, the response will have X-Auth-Token set to the token to use with Swift. :param req: The swob.Request to process. :returns: swob.Response, 2xx on success with data set as explained above. """ # Validate the request info try: pathsegs = split_path(req.path_info, 1, 3, True) except ValueError: self.logger.increment('errors') return HTTPNotFound(request=req) if not ((pathsegs[0] == 'v1' and pathsegs[2] == 'auth') or pathsegs[0] in ('auth', 'v1.0')): return HTTPBadRequest(request=req) return HTTPSeeOther(location=self.ext_authentication_url)
def __call__(self, env, start_response): """ Main hook into the WSGI paste.deploy filter/app pipeline. :param env: The WSGI environment dict. :param start_response: The WSGI start_response hook. """ env['staticweb.start_time'] = time.time() try: (version, account, container, obj) = \ split_path(env['PATH_INFO'], 2, 4, True) except ValueError: return self.app(env, start_response) if env['REQUEST_METHOD'] in ('PUT', 'POST') and container and not obj: memcache_client = cache_from_env(env) if memcache_client: memcache_key = \ '/staticweb/%s/%s/%s' % (version, account, container) memcache_client.delete(memcache_key) return self.app(env, start_response) if env['REQUEST_METHOD'] not in ('HEAD', 'GET'): return self.app(env, start_response) if env.get('REMOTE_USER') and \ not config_true_value(env.get('HTTP_X_WEB_MODE', 'f')): return self.app(env, start_response) if not container: return self.app(env, start_response) context = _StaticWebContext(self, version, account, container, obj) if obj: return context.handle_object(env, start_response) return context.handle_container(env, start_response)
def parse_raw_obj(obj_info): """ Translate a reconciler container listing entry to a dictionary containing the parts of the misplaced object queue entry. :param obj_info: an entry in an a container listing with the required keys: name, content_type, and hash :returns: a queue entry dict with the keys: q_policy_index, account, container, obj, q_op, q_ts, q_record, and path """ raw_obj_name = obj_info['name'].encode('utf-8') policy_index, obj_name = raw_obj_name.split(':', 1) q_policy_index = int(policy_index) account, container, obj = split_path(obj_name, 3, 3, rest_with_last=True) try: q_op = { 'application/x-put': 'PUT', 'application/x-delete': 'DELETE', }[obj_info['content_type']] except KeyError: raise ValueError('invalid operation type %r' % obj_info.get('content_type', None)) return { 'q_policy_index': q_policy_index, 'account': account, 'container': container, 'obj': obj, 'q_op': q_op, 'q_ts': Timestamp(obj_info['hash']), 'q_record': last_modified_date_to_timestamp( obj_info['last_modified']), 'path': '/%s/%s/%s' % (account, container, obj) }
def publish_sample(self, env, bytes_received, bytes_sent): req = REQUEST.Request(env) try: version, account, container, obj = utils.split_path(req.path, 2, 4, True) except ValueError: return now = timeutils.utcnow().isoformat() resource_metadata = { "path": req.path, "version": version, "container": container, "object": obj, } for header in self.metadata_headers: if header.upper() in req.headers: resource_metadata['http_header_%s' % header] = req.headers.get( header.upper()) with self.pipeline_manager.publisher( context.get_admin_context()) as publisher: if bytes_received: publisher([sample.Sample( name='storage.objects.incoming.bytes', type=sample.TYPE_DELTA, unit='B', volume=bytes_received, user_id=env.get('HTTP_X_USER_ID'), project_id=env.get('HTTP_X_TENANT_ID'), resource_id=account.partition('AUTH_')[2], timestamp=now, resource_metadata=resource_metadata)]) if bytes_sent: publisher([sample.Sample( name='storage.objects.outgoing.bytes', type=sample.TYPE_DELTA, unit='B', volume=bytes_sent, user_id=env.get('HTTP_X_USER_ID'), project_id=env.get('HTTP_X_TENANT_ID'), resource_id=account.partition('AUTH_')[2], timestamp=now, resource_metadata=resource_metadata)]) # publish the event for each request # request method will be recorded in the metadata resource_metadata['method'] = req.method.lower() publisher([sample.Sample( name='storage.api.request', type=sample.TYPE_DELTA, unit='request', volume=1, user_id=env.get('HTTP_X_USER_ID'), project_id=env.get('HTTP_X_TENANT_ID'), resource_id=account.partition('AUTH_')[2], timestamp=now, resource_metadata=resource_metadata)])
def request_with_policy(method, url, body=None, headers={}): version, account, container, obj = split_path(url, 1, 4, True) if policy_specified and method == 'PUT' and container and not obj \ and 'X-Storage-Policy' not in headers: headers['X-Storage-Policy'] = policy_specified return orig_request(method, url, body, headers)
def __call__(self, env, start_response): """ Main hook into the WSGI paste.deploy filter/app pipeline. :param env: The WSGI environment dict. :param start_response: The WSGI start_response hook. """ env['staticweb.start_time'] = time.time() try: (self.version, self.account, self.container, self.obj) = \ split_path(env['PATH_INFO'], 2, 4, True) except ValueError: return self.app(env, start_response) memcache_client = cache_from_env(env) if memcache_client: if env['REQUEST_METHOD'] in ('PUT', 'POST'): if not self.obj and self.container: memcache_key = '/staticweb/%s/%s/%s' % \ (self.version, self.account, self.container) memcache_client.delete(memcache_key) return self.app(env, start_response) if (env['REQUEST_METHOD'] not in ('HEAD', 'GET') or (env.get('REMOTE_USER') and env.get('HTTP_X_WEB_MODE', 'f').lower() not in TRUE_VALUES) or (not env.get('REMOTE_USER') and env.get('HTTP_X_WEB_MODE', 't').lower() not in TRUE_VALUES)): return self.app(env, start_response) if self.obj: return self._handle_object(env, start_response) elif self.container: return self._handle_container(env, start_response) return self.app(env, start_response)
def __call__(self, req): container = split_path(req.path, 1, 4, True)[2] if 'batch' == container: return self.handle_batch(req) return self.app
def check_path_header(req, name, length, error_msg): # FIXME: replace swift.common.constraints check_path_header # when swift3 supports swift 2.2 or later """ 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. """ src_header = unquote(req.headers.get(name)) if not src_header.startswith('/'): src_header = '/' + src_header try: return utils.split_path(src_header, length, length, True) except ValueError: raise HTTPPreconditionFailed( request=req, body=error_msg)
def authenticate(self, app): """ authenticate method will run pre-authenticate request and retrieve account information. Note that it currently supports only keystone and tempauth. (no support for the third party authentication middleware) """ sw_req = self.to_swift_req('TEST', None, None, body='') # don't show log message of this request sw_req.environ['swift.proxy_access_log_made'] = True sw_resp = sw_req.get_response(app) if not sw_req.remote_user: raise SignatureDoesNotMatch() _, self.account, _ = split_path(sw_resp.environ['PATH_INFO'], 2, 3, True) self.account = utf8encode(self.account) if 'HTTP_X_USER_NAME' in sw_resp.environ: # keystone self.user_id = "%s:%s" % (sw_resp.environ['HTTP_X_TENANT_NAME'], sw_resp.environ['HTTP_X_USER_NAME']) self.user_id = utf8encode(self.user_id) self.keystone_token = sw_req.environ['HTTP_X_AUTH_TOKEN'] else: # tempauth self.user_id = self.access_key
def DELETE(self, req): """Handle HTTP DELETE request.""" try: drive, part, account = split_path(unquote(req.path), 3) except ValueError, err: return HTTPBadRequest(body=str(err), content_type='text/plain', request=req)
def POST(self, req): """Handle HTTP POST request.""" try: drive, part, account, container = split_path(unquote(req.path), 4) validate_device_partition(drive, part) except ValueError, err: return HTTPBadRequest(body=str(err), content_type="text/plain", request=req)
def handle_extract(self, req, compress_type): """ :params req: a swob Request :params compress_type: specifying the compression type of the tar. Accepts '', 'gz, or 'bz2' :raises HTTPException: on unhandled errors :returns: a swob response to request """ success_count = 0 failed_files = [] existing_containers = set() out_content_type = req.accept.best_match(ACCEPTABLE_FORMATS) if not out_content_type: return HTTPNotAcceptable(request=req) if req.content_length is None and req.headers.get("transfer-encoding", "").lower() != "chunked": return HTTPBadRequest("Invalid request: no content sent.") try: vrs, account, extract_base = split_path(unquote(req.path), 2, 3, True) except ValueError: return HTTPNotFound(request=req) extract_base = extract_base or "" extract_base = extract_base.rstrip("/") try: tar = tarfile.open(mode="r|" + compress_type, fileobj=req.body_file) while True: tar_info = tar.next() if tar_info is None or len(failed_files) >= self.max_failed_extractions: break if tar_info.isfile(): obj_path = tar_info.name if obj_path.startswith("./"): obj_path = obj_path[2:] obj_path = obj_path.lstrip("/") if extract_base: obj_path = extract_base + "/" + obj_path if "/" not in obj_path: continue # ignore base level file destination = "/".join(["", vrs, account, obj_path]) container = obj_path.split("/", 1)[0] if not check_utf8(destination): failed_files.append([quote(destination[:MAX_PATH_LENGTH]), HTTPPreconditionFailed().status]) continue if tar_info.size > MAX_FILE_SIZE: failed_files.append([quote(destination[:MAX_PATH_LENGTH]), HTTPRequestEntityTooLarge().status]) continue if container not in existing_containers: try: self.create_container(req, "/".join(["", vrs, account, container])) existing_containers.add(container) except CreateContainerError, err: if err.status_int == HTTP_UNAUTHORIZED: return HTTPUnauthorized(request=req) failed_files.append([quote(destination[:MAX_PATH_LENGTH]), err.status]) continue except ValueError: failed_files.append([quote(destination[:MAX_PATH_LENGTH]), HTTP_BAD_REQUEST]) continue if len(existing_containers) > self.max_containers: return HTTPBadRequest("More than %d base level containers in tar." % self.max_containers)
def __call__(self, env, start_response): try: # /v1/a/c or /v1/a/c/o split_path(env["PATH_INFO"], 3, 4, True) is_container_or_object_req = True except ValueError: is_container_or_object_req = False headers = [("Content-Type", "text/plain"), ("Content-Length", str(sum(map(len, self.body))))] if is_container_or_object_req and self.policy_idx is not None: headers.append(("X-Backend-Storage-Policy-Index", str(self.policy_idx))) start_response(self.response_str, headers) while env["wsgi.input"].read(5): pass return self.body
def get_controller(self, path): """ Get the controller to handle a request. :param path: path from request :returns: tuple of (controller class, path dictionary) :raises: ValueError (thrown by split_path) if given invalid path """ if path == '/info': d = dict(version=None, expose_info=self.expose_info, disallowed_sections=self.disallowed_sections, admin_key=self.admin_key) return InfoController, d version, account, container, obj = split_path(path, 1, 4, True) d = dict(version=version, account_name=account, container_name=container, object_name=obj) if obj and container and account: return ObjectController, d elif container and account: return ContainerController, d elif account and not container and not obj: return AccountController, d return None, d
def __init__(self, app, req, logger): """ Constructor :param app: :param req: :raise: ValueError, HTTPNotFound, KeyAttribute, ImageInvalid """ self.app = app self.req = req self.logger = logger self.resp_dict = {'Response Status': HTTPCreated().status, 'Response Body': '', 'Number Files Created': 0} self.env = req.environ self.resize_dimensions = [TYPE_IMAGE_LARGE, TYPE_IMAGE_MEDIUM] try: self.version, self.account, self.container, self.obj = split_path(self.req.path, 1, 4, True) except ValueError: raise HTTPNotFound(request=self.req) if not self.obj: raise ImageInvalid("Not an Image") if not str.lower(self.obj.split(".")[-1]) in IMAGE_TYPE: raise ImageInvalid("Not an Image") self.request_body = self.env['wsgi.input'].read(int(self.env['CONTENT_LENGTH'])) flo = cStringIO.StringIO(self.request_body) try: self.orig_image = Image.open(flo) except IOError: raise ImageInvalid("Not an Image")
def handle_request(self, req): """ Entry point for auth requests (ones that match the self.auth_prefix). Should return a WSGI-style callable (such as swob.Response). :param req: swob.Request object """ req.start_time = time() handler = None try: version, account, user, _junk = split_path( req.path_info, minsegs=1, maxsegs=4, rest_with_last=True) except ValueError: self.logger.increment('errors') return HTTPNotFound(request=req) if version in ('v1', 'v1.0', 'auth'): if req.method == 'GET': handler = self.handle_get_token if not handler: self.logger.increment('errors') req.response = HTTPBadRequest(request=req) else: req.response = handler(req) return req.response
def get_controller(self, path): """ Get the controller to handle a request. 得到一个controller然后处理请求. controller的类型有account/container/object. 实际的请求是由controller处理的. controller内包含PUT/POST/DELETE/GET/HEAD等HTTP方法. :param path: path from request :returns: tuple of (controller class, path dictionary) :raises: ValueError (thrown by split_path) if given invalid path """ version, account, container, obj = split_path(path, 1, 4, True) d = dict(version=version, account_name=account, container_name=container, object_name=obj) if obj and container and account: return ObjectController, d elif container and account: return ContainerController, d elif account and not container and not obj: return AccountController, d return None, d
def __call__(self, env, start_response): try: req = self.update_request(Request(env)) version, account, container, obj = split_path(req.path, 1, 4, True) if obj and container and account: # An object has been touched, check if any policies applied to parent account/container if not PoliciesMiddleware.LOCAL_CALL_HEADER in req.headers: response = self.object_mod(req, obj) elif container and account: # A container has been touched, check if a policy is being applied to it if req.method == "POST": if PoliciesMiddleware.EXPIRY_META in req.headers: try: expireType, duration = self.validate_policy(req.headers[PoliciesMiddleware.EXPIRY_META]) response = self.container_mod(req, duration) except ValueError: req.headers[PoliciesMiddleware.EXPIRY_META] = None response = HTTPPreconditionFailed(request=req, body='Invalid Policy Type provided to Container') elif PoliciesMiddleware.REMOVE_EXPIRY_META in req.headers: response = self.container_mod(req, 0) except UnicodeError: err = HTTPPreconditionFailed( request=req, body='Invalid UTF8 or contains NULL') return err(env, start_response) try: if response is not None: # If there was a problem with a policy related request return the error return response(env, start_response) except NameError: pass return self.app(env, start_response)
def get_container_info(env, app, swift_source=None): """ Get the info structure for a container, based on env and app. This is useful to middlewares. """ cache = cache_from_env(env) if not cache: return None (version, account, container, obj) = \ split_path(env['PATH_INFO'], 2, 4, True) cache_key = get_container_memcache_key(account, container) # Use a unique environment cache key per container. If you copy this env # to make a new request, it won't accidentally reuse the old container info env_key = 'swift.%s' % cache_key if env_key not in env: container_info = cache.get(cache_key) if not container_info: resp = make_pre_authed_request( env, 'HEAD', '/%s/%s/%s' % (version, account, container), swift_source=swift_source, ).get_response(app) container_info = headers_to_container_info( resp.headers, resp.status_int) env[env_key] = container_info return env[env_key]
def DELETE(self, request): """Handle HTTP DELETE requests for the Swift Object Server.""" try: device, partition, account, container, obj = split_path(unquote(request.path), 5, 5, True) validate_device_partition(device, partition) except ValueError, e: return HTTPBadRequest(body=str(e), request=request, content_type="text/plain")
def handle_ring(self, env, start_response): """handle requests to /ring""" base, ringfile = split_path(env["PATH_INFO"], minsegs=1, maxsegs=2, rest_with_last=True) if ringfile not in self.ring_files: start_response("404 Not Found", [("Content-Type", "text/plain")]) return ["Not Found\r\n"] target = pathjoin(self.swiftdir, ringfile) try: self._validate_file(target) except LockTimeout: self.logger.exception("swiftdir locked for update") start_response("503 Service Unavailable", [("Content-Type", "application/octet-stream")]) return ["Service Unavailable\r\n"] except (OSError, IOError): self.logger.exception("Oops") start_response("503 Service Unavailable", [("Content-Type", "text/plain")]) return ["Service Unavailable\r\n"] if "HTTP_IF_NONE_MATCH" in env: if env["HTTP_IF_NONE_MATCH"] == self.current_md5[target]: headers = [("Content-Type", "application/octet-stream")] start_response("304 Not Modified", headers) return ["Not Modified\r\n"] if env["REQUEST_METHOD"] == "GET": headers = [("Content-Type", "application/octet-stream")] headers.append(("Etag", self.current_md5[target])) start_response("200 OK", headers) return FileIterable(target) elif env["REQUEST_METHOD"] == "HEAD": headers = [("Content-Type", "application/octet-stream")] headers.append(("Etag", self.current_md5[target])) start_response("200 OK", headers) return [] else: start_response("501 Not Implemented", [("Content-Type", "text/plain")]) return ["Not Implemented\r\n"]
def get_account_info(env, app, swift_source=None): """ Get the info structure for an account, based on env and app. This is useful to middlewares. Note: This call bypasses auth. Success does not imply that the request has authorization to the account_info. """ cache = cache_from_env(env) if not cache: return None (version, account, _junk, _junk) = \ split_path(env['PATH_INFO'], 2, 4, True) cache_key = get_account_memcache_key(account) # Use a unique environment cache key per account. If you copy this env # to make a new request, it won't accidentally reuse the old account info env_key = 'swift.%s' % cache_key if env_key not in env: account_info = cache.get(cache_key) if not account_info: resp = make_pre_authed_request( env, 'HEAD', '/%s/%s' % (version, account), swift_source=swift_source, ).get_response(app) account_info = headers_to_account_info( resp.headers, resp.status_int) env[env_key] = account_info return env[env_key]
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)
def test_pop_queue(self): x = expirer.ObjectExpirer({}, logger=self.logger, swift=FakeInternalClient({})) requests = [] def capture_requests(ipaddr, port, method, path, *args, **kwargs): requests.append((method, path)) with mocked_http_conn(200, 200, 200, give_connect=capture_requests) as fake_conn: x.pop_queue('a', 'c', 'o') with self.assertRaises(StopIteration): next(fake_conn.code_iter) for method, path in requests: self.assertEqual(method, 'DELETE') device, part, account, container, obj = utils.split_path( path, 5, 5, True) self.assertEqual(account, 'a') self.assertEqual(container, 'c') self.assertEqual(obj, 'o')
def get_controller(self, path): """ Get the controller to handle a request. :param path: path from request :returns: tuple of (controller class, path dictionary) :raises: ValueError (thrown by split_path) if given invalid path """ version, account, container, obj = split_path(path, 1, 4, True) d = dict(version=version, account_name=account, container_name=container, object_name=obj) if obj and container and account: return ObjectController, d elif container and account: return ContainerController, d elif account and not container and not obj: return AccountController, d return None, d
def __init__(self, env, path, swift_source=None): self.environ = env (version, account, container, obj) = split_path(path, 2, 4, True) self.account = account self.container = container self.obj = obj if obj: stype = 'object' self.headers = { 'content-length': 5555, 'content-type': 'text/plain' } else: stype = container and 'container' or 'account' self.headers = { 'x-%s-object-count' % (stype): 1000, 'x-%s-bytes-used' % (stype): 6666 } if swift_source: meta = 'x-%s-meta-fakerequest-swift-source' % stype self.headers[meta] = swift_source
def get_account_info(env, app, swift_source=None): """ Get the info structure for an account, based on env and app. This is useful to middlewares. Note: This call bypasses auth. Success does not imply that the request has authorization to the container. """ (version, account, _junk, _junk) = \ split_path(env['PATH_INFO'], 2, 4, True) info = get_info(app, env, account, ret_not_found=True, swift_source=swift_source) if not info: info = headers_to_account_info({}, 0) if info.get('container_count') is None: info['container_count'] = 0 else: info['container_count'] = int(info['container_count']) return info
def _is_definitive_auth(self, path): """ Determine if we are the definitive auth Determines if we are the definitive auth for a given path. If the account name is prefixed with something matching one of the reseller_prefix items, then we are the auth (return True) Non-matching: we are not the auth. However, one of the reseller_prefix items can be blank. If so, we cannot always be definite so return False. :param path: A path (e.g., /v1/AUTH_joesaccount/c/o) :return:True if we are definitive auth """ try: version, account, rest = split_path(path, 1, 3, True) except ValueError: return False if account: return bool(self._get_account_prefix(account)) return False
def get_controller(self, env, path): container, obj = split_path(path, 0, 2, True) d = { 'container_name': container, 'object_name': unquote(obj) if obj is not None else obj } if 'QUERY_STRING' in env: args = dict(urlparse.parse_qsl(env['QUERY_STRING'], 1)) else: args = {} if container and obj: if env['REQUEST_METHOD'] == 'POST': if 'uploads' or 'uploadId' in args: return BucketController, d return ObjectController, d elif container: return BucketController, d return ServiceController, d
def PUT(self, req): """ Handle PUT Object and PUT Object (Copy) request """ if CONF.s3_acl: if 'X-Amz-Copy-Source' in req.headers: src_path = req.headers['X-Amz-Copy-Source'] src_path = src_path if src_path.startswith('/') else \ ('/' + src_path) src_bucket, src_obj = split_path(src_path, 0, 2, True) req.get_response(self.app, 'HEAD', src_bucket, src_obj, permission='READ') b_resp = req.get_response(self.app, 'HEAD', obj='') # To avoid overwriting the existing object by unauthorized user, # we send HEAD request first before writing the object to make # sure that the target object does not exist or the user that sent # the PUT request have write permission. try: req.get_response(self.app, 'HEAD') except NoSuchKey: pass req_acl = ACL.from_headers(req.headers, b_resp.bucket_acl.owner, Owner(req.user_id, req.user_id)) req.object_acl = req_acl resp = req.get_response(self.app) if 'X-Amz-Copy-Source' in req.headers: elem = Element('CopyObjectResult') SubElement(elem, 'ETag').text = '"%s"' % resp.etag body = tostring(elem, use_s3ns=False) return HTTPOk(body=body, headers=resp.headers) resp.status = HTTP_OK return resp
def round_robin_order(self, task_iter): """ Change order of expiration tasks to avoid deleting objects in a certain container continuously. :param task_iter: An iterator of delete-task dicts, which should each have a ``target_path`` key. """ obj_cache = defaultdict(deque) cnt = 0 def dump_obj_cache_in_round_robin(): while obj_cache: for key in sorted(obj_cache): if obj_cache[key]: yield obj_cache[key].popleft() else: del obj_cache[key] for delete_task in task_iter: try: target_account, target_container, _junk = \ split_path('/' + delete_task['target_path'], 3, 3, True) cache_key = '%s/%s' % (target_account, target_container) # sanity except ValueError: self.logger.error('Unexcepted error handling task %r' % delete_task) continue obj_cache[cache_key].append(delete_task) cnt += 1 if cnt > MAX_OBJECTS_TO_CACHE: for task in dump_obj_cache_in_round_robin(): yield task cnt = 0 for task in dump_obj_cache_in_round_robin(): yield task
def authorize_anonymous(self, req): """ Authorize an anonymous request. :returns: None if authorization is granted, an error page otherwise. """ try: part = swift_utils.split_path(req.path, 1, 4, True) version, account, container, obj = part except ValueError: return HTTPNotFound(request=req) is_authoritative_authz = (account and account.startswith(self.reseller_prefix)) if not is_authoritative_authz: return self.denied_response(req) referrers, roles = swift_acl.parse_acl(getattr(req, 'acl', None)) authorized = self._authorize_unconfirmed_identity( req, obj, referrers, roles) if not authorized: return self.denied_response(req)
def action_routine(self, req, storage_url, token): """ execute action """ path = urlparse(self.del_prefix(req.url)).path vrs, acc, cont, obj = split_path(path, 1, 4, True) path_type = len([i for i in [vrs, acc, cont, obj] if i]) params = req.params_alt() self.logger.debug('Received Params: %s' % params) action = params.get('_action') lines = int(params.get('_line', self.items_per_page)) page = int(params.get('_page', 0)) marker = str(params.get('_marker', '')) cont_param = params.get('cont_name', None) obj_prefix = params.get('obj_prefix', '') if cont_param: cont = quote(cont_param) obj_param = params.get('obj_name', None) if obj_param and len(obj_param) == 2: obj_name, obj_fp = obj_param if obj_prefix: obj_name = obj_prefix + obj_name obj = quote(obj_name) else: obj_name, obj_fp = ('', None) from_cont = params.get('from_container', None) if from_cont: cont = quote(from_cont) from_obj = params.get('from_object', None) if from_obj: obj = from_obj to_cont = params.get('to_container', None) if to_cont: to_cont = to_cont to_obj = params.get('to_object', None) if to_obj: to_obj = quote(to_obj) try: meta_headers = self.metadata_check(params) except ValueError, err: return HTTP_PRECONDITION_FAILED
def get_controller(self, req): """ Get the controller to handle a request. :param req: the request :returns: tuple of (controller class, path dictionary) :raises ValueError: (thrown by split_path) if given invalid path """ if req.path == '/info': d = dict(version=None, expose_info=self.expose_info, disallowed_sections=self.disallowed_sections, admin_key=self.admin_key) return InfoController, d version, account, container, obj = split_path(req.path, 1, 4, True) d = dict(version=version, account_name=account, container_name=container, object_name=obj) return ProxySatelliteController, d
def FakeGetInfo(self, env, app, swift_source=None): info = { 'status': 0, 'sync_key': None, 'meta': {}, 'cors': { 'allow_origin': None, 'expose_headers': None, 'max_age': None }, 'sysmeta': {}, 'read_acl': None, 'object_count': None, 'write_acl': None, 'versions': None, 'bytes': None } info['storage_policy'] = self.policy_to_test (version, account, container, unused) = \ split_path(env['PATH_INFO'], 3, 4, True) self.assertEqual((version, account, container), self.expected_path[:3]) return info
def log_line_parser(self, raw_log): '''given a raw access log line, return a dict of the good parts''' d = {} try: (unused, server, client_ip, lb_ip, timestamp, method, request, http_version, code, referrer, user_agent, auth_token, bytes_in, bytes_out, etag, trans_id, headers, processing_time) = (unquote(x) for x in raw_log[16:].split(' ')[:18]) except ValueError: self.logger.debug(_('Bad line data: %s') % repr(raw_log)) return {} if server != self.server_name: # incorrect server name in log line self.logger.debug(_('Bad server name: found "%(found)s" ' \ 'expected "%(expected)s"') % {'found': server, 'expected': self.server_name}) return {} try: (version, account, container_name, object_name) = \ split_path(request, 2, 4, True) except ValueError, e: self.logger.debug(_('Invalid path: %(error)s from data: %(log)s') % {'error': e, 'log': repr(raw_log)}) return {}
def handle_request(self, req): """ Entry point for auth requests (ones that match the self.auth_prefix). Should return a WSGI-style callable (such as webob.Response). :param req: webob.Request object """ req.start_time = time() handler = None try: version, account, user, _junk = split_path(req.path_info, minsegs=1, maxsegs=4, rest_with_last=True) except ValueError: return HTTPNotFound(request=req) if version in ('v1', 'v1.0', 'auth'): if req.method == 'GET': handler = self.handle_get_token if not handler: req.response = HTTPBadRequest(request=req) else: req.response = handler(req) return req.response
def parse_raw_obj(obj_info): """ Translate a reconciler container listing entry to a dictionary containing the parts of the misplaced object queue entry. :param obj_info: an entry in an a container listing with the required keys: name, content_type, and hash :returns: a queue entry dict with the keys: q_policy_index, account, container, obj, q_op, q_ts, q_record, and path """ if six.PY2: raw_obj_name = obj_info['name'].encode('utf-8') else: raw_obj_name = obj_info['name'] policy_index, obj_name = raw_obj_name.split(':', 1) q_policy_index = int(policy_index) account, container, obj = split_path(obj_name, 3, 3, rest_with_last=True) try: q_op = { 'application/x-put': 'PUT', 'application/x-delete': 'DELETE', }[obj_info['content_type']] except KeyError: raise ValueError('invalid operation type %r' % obj_info.get('content_type', None)) return { 'q_policy_index': q_policy_index, 'account': account, 'container': container, 'obj': obj, 'q_op': q_op, 'q_ts': decode_timestamps((obj_info['hash']))[0], 'q_record': last_modified_date_to_timestamp( obj_info['last_modified']), 'path': '/%s/%s/%s' % (account, container, obj) }
def __call__(self, env, start_response): content_type = env.get('CONTENT_TYPE', '') path = env['PATH_INFO'] version, account, container, object = swift_utils.split_path( path, minsegs=2, maxsegs=4, rest_with_last=True) self.logger.debug('generatethumb: %s, %s, %s' % (env['REQUEST_METHOD'], content_type, object)) req = Request(env) #self.logger.debug('generatethumb: class of req %s' % req.__class__) # don't generate the thumbnail for a thumbnailed image if 'X-Object-Transient-Sysmeta-Thumbnail' in req.headers: self.logger.debug( 'generatethumb: image already a thumbnail, skipping') return self.app(env, start_response) if object is not None and env['REQUEST_METHOD'] in [ "PUT", "POST" ] and content_type in self.content_types: #self.logger.debug('generatethumb: dir of wsgi.input: %s, class: %s' % (dir(env['wsgi.input']), env['wsgi.input'].__class__)) req.make_body_seekable() # currently I have a problem if executing the thumbnail generation code in the posthook, I cannot properly do a make_body_seekable # in the posthook, getting a client disconnected error, maybe this is due to the body stream not being avail anymore in the hook. #env['eventlet.posthooks'].append( # (self.generate_thumbnail, (req,), {}) #) # debug: direct call self.generate_thumbnail(env, req) return self.app(env, start_response)
def split_path(self, minsegs=1, maxsegs=None, rest_with_last=False): """ Validate and split the Request's path. **Examples**:: ['a'] = split_path('/a') ['a', None] = split_path('/a', 1, 2) ['a', 'c'] = split_path('/a/c', 1, 2) ['a', 'c', 'o/r'] = split_path('/a/c/o/r', 1, 3, True) :param minsegs: Minimum number of segments to be extracted :param maxsegs: Maximum number of segments to be extracted :param rest_with_last: If True, trailing data will be returned as part of last segment. If False, and there is trailing data, raises ValueError. :returns: list of segments with a length of maxsegs (non-existant segments will return as None) :raises: ValueError if given an invalid path """ return split_path( self.environ.get('SCRIPT_NAME', '') + self.environ['PATH_INFO'], minsegs, maxsegs, rest_with_last)
def authorize_via_acl(self, req): """Anon request handling. For now this only allows anon read of objects. Container and account actions are prohibited. """ self.log.debug('authorizing anonymous request') try: version, account, container, obj = split_path(req.path, 1, 4, True) except ValueError: return HTTPNotFound(request=req) if obj: return self._authorize_anon_object(req, account, container, obj) if container: return self._authorize_anon_container(req, account, container) if account: return self._authorize_anon_account(req, account) return self._authorize_anon_toplevel(req)
def check_path_header(req, name, length, error_msg): # FIXME: replace swift.common.constraints check_path_header # when swift3 supports swift 2.2 or later """ 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. """ src_header = unquote(req.headers.get(name)) if not src_header.startswith('/'): src_header = '/' + src_header try: return utils.split_path(src_header, length, length, True) except ValueError: raise HTTPPreconditionFailed(request=req, body=error_msg)
def __call__(self, req): print(req) print(req.headers) print(req.path_info) obj = None try: (version, account, container, obj) = \ split_path(req.path_info, 4, 4, True) if req.method == 'PUT': print(obj) payload = { "conf": { "swift_id": obj, "swift_container": container, "swift_user": account, "swift_version": version } } rep = requests.post(URL + ENDPOINT_PATH + "/dags/" + DAG_NAME + "/dag_runs", data=json.dumps(payload)) print(rep.text) self.logger.info(rep.headers) self.logger.info(rep.text) # No response return = bug resp = req.get_response(self.app) return resp except ValueError: # not an object request resp = req.get_response(self.app) return resp resp = req.get_response(self.app) return resp
def __call__(self, env, start_response): request = Request(env) url_prefix = '/object_endpoint/' if request.path.startswith(url_prefix): if request.method != 'GET': raise HTTPMethodNotAllowed() aco = split_path(request.path[len(url_prefix) - 1:], 1, 3, True) account = aco[0] container = aco[1] obj = aco[2] if obj.endswith('/'): obj = obj[:-1] object_partition, objects = self.object_ring.get_nodes( account, container, obj) endpoint_template = 'http://{ip}:{port}/{device}/{partition}/' + \ '{account}/{container}/{obj}' endpoints = [] for element in objects: endpoint = endpoint_template.format(ip=element['ip'], port=element['port'], device=element['device'], partition=object_partition, account=account, container=container, obj=obj) endpoints.append(endpoint) start_response('200 OK', {}) return json.dumps(endpoints) return self.app(env, start_response)
def add_file(self, env, req): try: self.logger.debug('indexelastic: start async add: %s' % (env)) path = env['PATH_INFO'] container, object = swift_utils.split_path(path, minsegs=1, maxsegs=2, rest_with_last=True) connections.create_connection(hosts=self.endpoints) File.init() f = File() f.bucket = container f.path = '/' + object f.timestamp = int(float(env['HTTP_X_TIMESTAMP']) * 1000) #f.user = env['HTTP_X_USER'] f.mimetype = env['CONTENT_TYPE'] # adding meta data metaprefix = 'X-Object-Meta-Imgmeta-' metakeys = [k for k in req.headers if k.startswith(metaprefix)] for k in metakeys: #self.logger.debug('header %s -> %s' % (h, req.headers[h])) k_short = k.replace(metaprefix, '') f.metadata[k_short] = req.headers[k] f.save() self.logger.debug('indexelastic: object with id %s added' % f.meta.id) except Exception: self.logger.exception( 'indexelastic: encountered exception while indexing in elastic search' )
def get_info_1(container_ring, obj_path, logger): path_comps = split_path(obj_path, 1, 3, True) account_name = path_comps[0] container_name = path_comps[1] obj_name = path_comps[2] container_part, container_nodes = \ container_ring.get_nodes(account_name, container_name) if not container_nodes: raise ContainerError() # Perhaps we should do something about the way we select the container # nodes. For now we just shuffle. It spreads the load, but it does not # improve upon the the case when some nodes are down, so auditor slows # to a crawl (if this plugin is enabled). random.shuffle(container_nodes) err_flag = 0 for node in container_nodes: try: headers, objs = direct_get_container( node, container_part, account_name, container_name, prefix=obj_name, limit=1) except (ClientException, Timeout): # Something is wrong with that server, treat as an error. err_flag += 1 continue if objs and objs[0]['name'] == obj_name: return objs[0] # We only report the object as dark if all known servers agree that it is. if err_flag: raise ContainerError() return None
def __call__(self, env, start_response): content_type = env.get('CONTENT_TYPE', '') path = env['PATH_INFO'] version, account, container, object = swift_utils.split_path( path, minsegs=2, maxsegs=4, rest_with_last=True) self.logger.debug('indexelastic: %s, %s, %s' % (env['REQUEST_METHOD'], content_type, object)) req = Request(env) # don't index thumbnail files (TODO: is it ok like this?) if 'X-Object-Transient-Sysmeta-Thumbnail' in req.headers: self.logger.debug('indexelastic: image is a thumbnail, skipping') return self.app(env, start_response) if object is not None and env['REQUEST_METHOD'] in ["PUT", "POST"]: env['eventlet.posthooks'].append((self.add_file, (req, ), {})) elif object is not None and env['REQUEST_METHOD'] in ["DELETE"]: env['eventlet.posthooks'].append((self.remove_file, (req, ), {})) return self.app(env, start_response)
def handle_token(self, request): """ Handles ReST requests from Swift to validate tokens Valid URL paths: * GET /token/<token> If the HTTP request returns with a 204, then the token is valid, the TTL of the token will be available in the X-Auth-Ttl header, and a comma separated list of the "groups" the user belongs to will be in the X-Auth-Groups header. :param request: webob.Request object """ try: _junk, token = split_path(request.path, minsegs=2) except ValueError: return HTTPBadRequest() # Retrieves (TTL, account, user, cfaccount) if valid, False otherwise headers = {} if 'Authorization' in request.headers: validation = self.validate_s3_sign(request, token) if validation: headers['X-Auth-Account-Suffix'] = validation[3] else: validation = self.validate_token(token) if not validation: return HTTPNotFound() groups = ['%s:%s' % (validation[1], validation[2]), validation[1]] if validation[3]: # admin access to a cfaccount or ".reseller_admin" to access to all # accounts, including creating new ones. groups.append(validation[3]) headers['X-Auth-TTL'] = validation[0] headers['X-Auth-Groups'] = ','.join(groups) return HTTPNoContent(headers=headers)
def __call__(self, request): try: (version, account, container, objname) = split_path(request.path_info, 4, 4, True) except ValueError: return self.app preview_path = '/%s/%s/%s_%s/%s' % (version, account, container, self.suffix, objname) if request.method == 'GET' and request.params.has_key('preview'): request.path_info = preview_path if request.method == 'PUT': if hasattr(request, 'body_file'): data = "" while True: chunk = request.body_file.read() if not chunk: break data += chunk request.body = data preview = create_preview(data) else: preview = create_preview(request.body) if preview: sub = wsgi.make_subrequest(request.environ, path=preview_path, body=preview) sub.get_response(self.app) if request.method == 'DELETE': sub = wsgi.make_subrequest(request.environ, path=preview_path) sub.get_response(self.app) return self.app
def __call__(self, env, start_response): """ Main hook into the WSGI paste.deploy filter/app pipeline. :param env: The WSGI environment dict. :param start_response: The WSGI start_response hook. """ env['staticweb.start_time'] = time.time() try: (version, account, container, obj) = \ split_path(env['PATH_INFO'], 2, 4, True) except ValueError: return self.app(env, start_response) if env['REQUEST_METHOD'] not in ('HEAD', 'GET'): return self.app(env, start_response) if env.get('REMOTE_USER') and \ not config_true_value(env.get('HTTP_X_WEB_MODE', 'f')): return self.app(env, start_response) if not container: return self.app(env, start_response) context = _StaticWebContext(self, version, account, container, obj) if obj: return context.handle_object(env, start_response) return context.handle_container(env, start_response)
def http_connect(ipaddr, port, device, partition, method, path, headers=None, query_string=None): a, c, o = split_path(path, 1, 3, True) if o: func = object_func elif c: func = container_func else: func = account_func resp = func(ipaddr, port, device, partition, method, path, headers=headers, query_string=query_string) return resp
def __call__(self, env, start_response): """ WSGI entry point. Wraps env in webob.Request object and passes it down. :param env: WSGI environment dictionary :param start_response: WSGI callable """ req = Request(env) if self.memcache_client is None: self.memcache_client = cache_from_env(env) if not self.memcache_client: self.logger.warning( _('Warning: Cannot ratelimit without a memcached client')) return self.app(env, start_response) try: version, account, container, obj = split_path(req.path, 1, 4, True) except ValueError: return self.app(env, start_response) ratelimit_resp = self.handle_ratelimit(req, account, container, obj) if ratelimit_resp is None: return self.app(env, start_response) else: return ratelimit_resp(env, start_response)