def test_pre_auth_wsgi_input(self): oldenv = {} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue("wsgi.input" in newenv) self.assertEquals(newenv["wsgi.input"].read(), "") oldenv = {"wsgi.input": StringIO("original wsgi.input")} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue("wsgi.input" in newenv) self.assertEquals(newenv["wsgi.input"].read(), "")
def test_pre_auth_wsgi_input(self): oldenv = {} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue('wsgi.input' in newenv) self.assertEquals(newenv['wsgi.input'].read(), '') oldenv = {'wsgi.input': StringIO('original wsgi.input')} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue('wsgi.input' in newenv) self.assertEquals(newenv['wsgi.input'].read(), '')
def iter_objects_by_prefix(account, container, prefix, swift_client=None, app=None): marker = '' while True: param = 'format=json&marker=%s' % marker if marker == '': param = '%s&prefix=%s' % (param, prefix) if swift_client: path = swift_client.make_path(account, container) resp = swift_client.make_request('GET', '%s?%s' % (path, param), {}, (2, 4)) elif app: path = '/v1/%s/%s' % (account, container) env = make_pre_authed_env({}, method='GET', path=path, query_string=param) req = make_pre_authed_request(env) resp = req.get_response(app) if not resp.status_int == 200: break data = json.loads(resp.body) if not data: break for item in data: yield item marker = data[-1]['name'].encode('utf8')
def handle_object(self, env, start_response): """ Handles a possible static web request for an object. This object could resolve into an index or listing request. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. """ tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') resp = self._app_call(tmp_env) status_int = self._get_status_int() if status_int // 100 in (2, 3): start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int != 404: return self._error_response(resp, env, start_response) self._get_container_info(env) if not self._listings and not self._index: return self.app(env, start_response) status_int = 404 if self._index: tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') if tmp_env['PATH_INFO'][-1] != '/': tmp_env['PATH_INFO'] += '/' tmp_env['PATH_INFO'] += self._index resp = self._app_call(tmp_env) status_int = self._get_status_int() if status_int // 100 in (2, 3): if env['PATH_INFO'][-1] != '/': resp = HTTPMovedPermanently( location=env['PATH_INFO'] + '/') self._log_response(env, resp.status_int) return resp(env, start_response) start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int == 404: if env['PATH_INFO'][-1] != '/': tmp_env = make_pre_authed_env(env, 'GET', '/%s/%s/%s' % (self.version, self.account, self.container), self.agent) tmp_env['QUERY_STRING'] = 'limit=1&format=json&delimiter' \ '=/&limit=1&prefix=%s' % quote(self.obj + '/') resp = self._app_call(tmp_env) body = ''.join(resp) if self._get_status_int() // 100 != 2 or not body or \ not json.loads(body): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) resp = HTTPMovedPermanently(location=env['PATH_INFO'] + '/') self._log_response(env, resp.status_int) return resp(env, start_response) return self._listing(env, start_response, self.obj)
def _error_response(self, response, env, start_response): """ Sends the error response to the remote client, possibly resolving a custom error response body based on x-container-meta-web-error. :param response: The error response we should default to sending. :param env: The original request WSGI environment. :param start_response: The WSGI start_response hook. """ if not self._error: start_response(self._response_status, self._response_headers, self._response_exc_info) return response save_response_status = self._response_status save_response_headers = self._response_headers save_response_exc_info = self._response_exc_info resp = self._app_call( make_pre_authed_env(env, 'GET', '/%s/%s/%s/%s%s' % (self.version, self.account, self.container, self._get_status_int(), self._error), self.agent, swift_source='SW')) if is_success(self._get_status_int()): start_response(save_response_status, self._response_headers, self._response_exc_info) return resp start_response(save_response_status, save_response_headers, save_response_exc_info) return response
def _get_key(self, env, account): """ Returns the X-Account-Meta-Temp-URL-Key header value for the account, or None if none is set. :param env: The WSGI environment for the request. :param account: Account str. :returns: X-Account-Meta-Temp-URL-Key str value, or None. """ key = None memcache = env.get('swift.cache') if memcache: key = memcache.get('temp-url-key/%s' % account) if not key: newenv = make_pre_authed_env(env, 'HEAD', '/v1/' + account, self.agent) newenv['CONTENT_LENGTH'] = '0' newenv['wsgi.input'] = StringIO('') key = [None] def _start_response(status, response_headers, exc_info=None): for h, v in response_headers: if h.lower() == 'x-account-meta-temp-url-key': key[0] = v i = iter(self.app(newenv, _start_response)) try: i.next() except StopIteration: pass key = key[0] if key and memcache: memcache.set('temp-url-key/%s' % account, key, timeout=60) return key
def handle_object(self, env, start_response): """ Handles a possible static web request for an object. This object could resolve into an index or listing request. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. """ tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') resp = self._app_call(tmp_env) status_int = self._get_status_int() if is_success(status_int) or is_redirection(status_int): start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int != HTTP_NOT_FOUND: return self._error_response(resp, env, start_response) self._get_container_info(env) if not self._listings and not self._index: return self.app(env, start_response) status_int = HTTP_NOT_FOUND if self._index: tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') if tmp_env['PATH_INFO'][-1] != '/': tmp_env['PATH_INFO'] += '/' tmp_env['PATH_INFO'] += self._index resp = self._app_call(tmp_env) status_int = self._get_status_int() if is_success(status_int) or is_redirection(status_int): if env['PATH_INFO'][-1] != '/': resp = HTTPMovedPermanently( location=env['PATH_INFO'] + '/') self._log_response(env, resp.status_int) return resp(env, start_response) start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int == HTTP_NOT_FOUND: if env['PATH_INFO'][-1] != '/': tmp_env = make_pre_authed_env(env, 'GET', '/%s/%s/%s' % (self.version, self.account, self.container), self.agent) tmp_env['QUERY_STRING'] = 'limit=1&format=json&delimiter' \ '=/&limit=1&prefix=%s' % quote(self.obj + '/') resp = self._app_call(tmp_env) body = ''.join(resp) if not is_success(self._get_status_int()) or not body or \ not json.loads(body): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) resp = HTTPMovedPermanently(location=env['PATH_INFO'] + '/') self._log_response(env, resp.status_int) return resp(env, start_response) return self._listing(env, start_response, self.obj)
def _error_response(self, response, env, start_response): """ Sends the error response to the remote client, possibly resolving a custom error response body based on x-container-meta-web-error. :param response: The error response we should default to sending. :param env: The original request WSGI environment. :param start_response: The WSGI start_response hook. """ if not self._error: start_response(self._response_status, self._response_headers, self._response_exc_info) return response save_response_status = self._response_status save_response_headers = self._response_headers save_response_exc_info = self._response_exc_info resp = self._app_call(make_pre_authed_env( env, 'GET', '/%s/%s/%s/%s%s' % ( self.version, self.account, self.container, self._get_status_int(), self._error), self.agent, swift_source='SW')) if is_success(self._get_status_int()): start_response(save_response_status, self._response_headers, self._response_exc_info) return resp start_response(save_response_status, save_response_headers, save_response_exc_info) return response
def _perform_subrequest(self, orig_env, attributes, fp, key): """ Performs the subrequest and returns the response. :param orig_env: The WSGI environment dict; will only be used to form a new env for the subrequest. :param attributes: dict of the attributes of the form so far. :param fp: The file-like object containing the request body. :param key: The account key to validate the signature with. :returns: (status_line, message) """ if not key: return '401 Unauthorized', 'invalid signature' try: max_file_size = int(attributes.get('max_file_size') or 0) except ValueError: raise FormInvalid('max_file_size not an integer') subenv = make_pre_authed_env(orig_env, 'PUT', agent=None, swift_source='FP') if 'QUERY_STRING' in subenv: del subenv['QUERY_STRING'] subenv['HTTP_TRANSFER_ENCODING'] = 'chunked' subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size) if subenv['PATH_INFO'][-1] != '/' and \ subenv['PATH_INFO'].count('/') < 4: subenv['PATH_INFO'] += '/' subenv['PATH_INFO'] += attributes['filename'] or 'filename' if 'content-type' in attributes: subenv['CONTENT_TYPE'] = \ attributes['content-type'] or 'application/octet-stream' elif 'CONTENT_TYPE' in subenv: del subenv['CONTENT_TYPE'] try: if int(attributes.get('expires') or 0) < time(): return '401 Unauthorized', 'form expired' except ValueError: raise FormInvalid('expired not an integer') hmac_body = '%s\n%s\n%s\n%s\n%s' % ( orig_env['PATH_INFO'], attributes.get('redirect') or '', attributes.get('max_file_size') or '0', attributes.get('max_file_count') or '0', attributes.get('expires') or '0') sig = hmac.new(key, hmac_body, sha1).hexdigest() if not streq_const_time(sig, (attributes.get('signature') or 'invalid')): return '401 Unauthorized', 'invalid signature' substatus = [None] def _start_response(status, headers, exc_info=None): substatus[0] = status i = iter(self.app(subenv, _start_response)) try: i.next() except StopIteration: pass return substatus[0], ''
def test_pre_auth_wsgi_input(self): oldenv = {} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue('wsgi.input' in newenv) self.assertEquals(newenv['wsgi.input'].read(), '') oldenv = {'wsgi.input': StringIO('original wsgi.input')} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue('wsgi.input' in newenv) self.assertEquals(newenv['wsgi.input'].read(), '') oldenv = {'swift.source': 'UT'} newenv = wsgi.make_pre_authed_env(oldenv) self.assertEquals(newenv['swift.source'], 'UT') oldenv = {'swift.source': 'UT'} newenv = wsgi.make_pre_authed_env(oldenv, swift_source='SA') self.assertEquals(newenv['swift.source'], 'SA')
def test_pre_auth_wsgi_input(self): oldenv = {} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue("wsgi.input" in newenv) self.assertEquals(newenv["wsgi.input"].read(), "") oldenv = {"wsgi.input": StringIO("original wsgi.input")} newenv = wsgi.make_pre_authed_env(oldenv) self.assertTrue("wsgi.input" in newenv) self.assertEquals(newenv["wsgi.input"].read(), "") oldenv = {"swift.source": "UT"} newenv = wsgi.make_pre_authed_env(oldenv) self.assertEquals(newenv["swift.source"], "UT") oldenv = {"swift.source": "UT"} newenv = wsgi.make_pre_authed_env(oldenv, swift_source="SA") self.assertEquals(newenv["swift.source"], "SA")
def _prepare_pre_auth_info_request(env, path): """ Prepares a pre authed request to obtain info using a HEAD. :param env: the environment used by the current request :param path: The unquoted request path :returns: the pre authed request """ # Set the env for the pre_authed call without a query string newenv = make_pre_authed_env(env, "HEAD", path, agent="Swift", query_string="", swift_source="GET_INFO") # Note that Request.blank expects quoted path return Request.blank(quote(path), environ=newenv)
def _prepare_pre_auth_info_request(env, path): """ Prepares a pre authed request to obtain info using a HEAD. :param env: the environment used by the current request :param path: The unquoted request path :returns: the pre authed request """ # Set the env for the pre_authed call without a query string newenv = make_pre_authed_env(env, 'HEAD', path, agent='Swift', query_string='', swift_source='GET_INFO') # Note that Request.blank expects quoted path return Request.blank(quote(path), environ=newenv)
def handle_object(self, env, start_response): """ Handles a possible static web request for an object. This object could resolve into an index or listing request. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. """ tmp_env = dict(env) tmp_env["HTTP_USER_AGENT"] = "%s StaticWeb" % env.get("HTTP_USER_AGENT") resp = self._app_call(tmp_env) status_int = self._get_status_int() if is_success(status_int) or is_redirection(status_int): start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int != HTTP_NOT_FOUND: return self._error_response(resp, env, start_response) self._get_container_info(env) if not self._listings and not self._index: return self.app(env, start_response) status_int = HTTP_NOT_FOUND if self._index: tmp_env = dict(env) tmp_env["HTTP_USER_AGENT"] = "%s StaticWeb" % env.get("HTTP_USER_AGENT") if tmp_env["PATH_INFO"][-1] != "/": tmp_env["PATH_INFO"] += "/" tmp_env["PATH_INFO"] += self._index resp = self._app_call(tmp_env) status_int = self._get_status_int() if is_success(status_int) or is_redirection(status_int): if env["PATH_INFO"][-1] != "/": resp = HTTPMovedPermanently(location=env["PATH_INFO"] + "/") self._log_response(env, resp.status_int) return resp(env, start_response) start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int == HTTP_NOT_FOUND: if env["PATH_INFO"][-1] != "/": tmp_env = make_pre_authed_env( env, "GET", "/%s/%s/%s" % (self.version, self.account, self.container), self.agent ) tmp_env["QUERY_STRING"] = "limit=1&format=json&delimiter" "=/&limit=1&prefix=%s" % quote(self.obj + "/") resp = self._app_call(tmp_env) body = "".join(resp) if not is_success(self._get_status_int()) or not body or not json.loads(body): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) resp = HTTPMovedPermanently(location=env["PATH_INFO"] + "/") self._log_response(env, resp.status_int) return resp(env, start_response) return self._listing(env, start_response, self.obj)
def _prepare_pre_auth_info_request(env, path, swift_source): """ Prepares a pre authed request to obtain info using a HEAD. :param env: the environment used by the current request :param path: The unquoted request path :param swift_source: value for swift.source in WSGI environment :returns: the pre authed request """ # Set the env for the pre_authed call without a query string newenv = make_pre_authed_env(env, 'HEAD', path, agent='Swift', query_string='', swift_source=swift_source) # Note that Request.blank expects quoted path return Request.blank(quote(path), environ=newenv)
def _prepare_pre_auth_info_request(env, path, swift_source): """ Prepares a pre authed request to obtain info using a HEAD. :param env: the environment used by the current request :param path: The unquoted request path :param swift_source: value for swift.source in WSGI environment :returns: the pre authed request """ # Set the env for the pre_authed call without a query string newenv = make_pre_authed_env(env, 'HEAD', path, agent='Swift', query_string='', swift_source=swift_source) # This is a sub request for container metadata- drop the Origin header from # the request so the it is not treated as a CORS request. newenv.pop('HTTP_ORIGIN', None) # Note that Request.blank expects quoted path return Request.blank(quote(path), environ=newenv)
def _get_key(self, env): """ Returns the X-Account-Meta-Temp-URL-Key header value for the account, or None if none is set. :param env: The WSGI environment for the request. :returns: X-Account-Meta-Temp-URL-Key str value, or None. """ parts = env['PATH_INFO'].split('/', 4) if len(parts) < 4 or parts[0] or parts[1] != 'v1' or not parts[2] or \ not parts[3]: return None account = parts[2] key = None memcache = env.get('swift.cache') if memcache: key = memcache.get('temp-url-key/%s' % account) if not key: newenv = make_pre_authed_env(env, 'HEAD', '/v1/' + account, agent=None, swift_source='FP') if 'QUERY_STRING' in newenv: del newenv['QUERY_STRING'] newenv['CONTENT_LENGTH'] = '0' newenv['wsgi.input'] = StringIO('') key = [None] def _start_response(status, response_headers, exc_info=None): for h, v in response_headers: if h.lower() == 'x-account-meta-temp-url-key': key[0] = v i = iter(self.app(newenv, _start_response)) try: i.next() except StopIteration: pass key = key[0] if key and memcache: memcache.set('temp-url-key/%s' % account, key, time=60) return key
def _get_keys(self, env, account): """ Returns the X-Account-Meta-Temp-URL-Key[-2] header values for the account, or an empty list if none is set. Returns 0, 1, or 2 elements depending on how many keys are set in the account's metadata. :param env: The WSGI environment for the request. :param account: Account str. :returns: [X-Account-Meta-Temp-URL-Key str value if set, X-Account-Meta-Temp-URL-Key-2 str value if set] """ keys = None memcache = env.get('swift.cache') memcache_hash_key = 'temp-url-keys/%s' % account if memcache: keys = memcache.get(memcache_hash_key) if keys is None: newenv = make_pre_authed_env(env, 'HEAD', '/v1/' + account, self.agent, swift_source='TU') newenv['CONTENT_LENGTH'] = '0' newenv['wsgi.input'] = StringIO('') keys = [] def _start_response(status, response_headers, exc_info=None): for h, v in response_headers: if h.lower() == 'x-account-meta-temp-url-key': keys.append(v) elif h.lower() == 'x-account-meta-temp-url-key-2': keys.append(v) i = iter(self.app(newenv, _start_response)) try: i.next() except StopIteration: pass if memcache: timeout = 60 if keys else 6 memcache.set(memcache_hash_key, keys, time=timeout) return keys
def handle_object(self, env, start_response): """ Handles a possible static web request for an object. This object could resolve into an index or listing request. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. """ tmp_env = dict(env) tmp_env["HTTP_USER_AGENT"] = "%s StaticWeb" % env.get("HTTP_USER_AGENT") tmp_env["swift.source"] = "SW" resp = self._app_call(tmp_env) status_int = self._get_status_int() self._get_container_info(env) if is_success(status_int) or is_redirection(status_int): # Treat directory marker objects as not found if not self._dir_type: self._dir_type = "application/directory" content_length = self._response_header_value("content-length") content_length = int(content_length) if content_length else 0 if self._response_header_value("content-type") == self._dir_type and content_length <= 1: status_int = HTTP_NOT_FOUND else: start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int != HTTP_NOT_FOUND: # Retaining the previous code's behavior of not using custom error # pages for non-404 errors. self._error = None return self._error_response(resp, env, start_response) if not self._listings and not self._index: return self.app(env, start_response) status_int = HTTP_NOT_FOUND if self._index: tmp_env = dict(env) tmp_env["HTTP_USER_AGENT"] = "%s StaticWeb" % env.get("HTTP_USER_AGENT") tmp_env["swift.source"] = "SW" if tmp_env["PATH_INFO"][-1] != "/": tmp_env["PATH_INFO"] += "/" tmp_env["PATH_INFO"] += self._index resp = self._app_call(tmp_env) status_int = self._get_status_int() if is_success(status_int) or is_redirection(status_int): if env["PATH_INFO"][-1] != "/": resp = HTTPMovedPermanently(location=env["PATH_INFO"] + "/") return resp(env, start_response) start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int == HTTP_NOT_FOUND: if env["PATH_INFO"][-1] != "/": tmp_env = make_pre_authed_env( env, "GET", "/%s/%s/%s" % (self.version, self.account, self.container), self.agent, swift_source="SW", ) tmp_env["QUERY_STRING"] = "limit=1&format=json&delimiter" "=/&limit=1&prefix=%s" % quote(self.obj + "/") resp = self._app_call(tmp_env) body = "".join(resp) if not is_success(self._get_status_int()) or not body or not json.loads(body): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) resp = HTTPMovedPermanently(location=env["PATH_INFO"] + "/") return resp(env, start_response) return self._listing(env, start_response, self.obj)
def _listing(self, env, start_response, prefix=None): """ Sends an HTML object listing to the remote client. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. :param prefix: Any prefix desired for the container listing. """ if not config_true_value(self._listings): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) tmp_env = make_pre_authed_env( env, 'GET', '/%s/%s/%s' % ( self.version, self.account, self.container), self.agent, swift_source='SW') tmp_env['QUERY_STRING'] = 'delimiter=/&format=json' if prefix: tmp_env['QUERY_STRING'] += '&prefix=%s' % quote(prefix) else: prefix = '' resp = self._app_call(tmp_env) if not is_success(self._get_status_int()): return self._error_response(resp, env, start_response) listing = None body = ''.join(resp) if body: listing = json.loads(body) if not listing: resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) headers = {'Content-Type': 'text/html; charset=UTF-8'} body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 ' \ 'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' \ '<html>\n' \ ' <head>\n' \ ' <title>Listing of %s</title>\n' % \ cgi.escape(env['PATH_INFO']) if self._listings_css: body += ' <link rel="stylesheet" type="text/css" ' \ 'href="%s" />\n' % (self._build_css_path(prefix)) else: body += ' <style type="text/css">\n' \ ' h1 {font-size: 1em; font-weight: bold;}\n' \ ' th {text-align: left; padding: 0px 1em 0px 1em;}\n' \ ' td {padding: 0px 1em 0px 1em;}\n' \ ' a {text-decoration: none;}\n' \ ' </style>\n' body += ' </head>\n' \ ' <body>\n' \ ' <h1 id="title">Listing of %s</h1>\n' \ ' <table id="listing">\n' \ ' <tr id="heading">\n' \ ' <th class="colname">Name</th>\n' \ ' <th class="colsize">Size</th>\n' \ ' <th class="coldate">Date</th>\n' \ ' </tr>\n' % \ cgi.escape(env['PATH_INFO']) if prefix: body += ' <tr id="parent" class="item">\n' \ ' <td class="colname"><a href="../">../</a></td>\n' \ ' <td class="colsize"> </td>\n' \ ' <td class="coldate"> </td>\n' \ ' </tr>\n' for item in listing: if 'subdir' in item: if isinstance(item['subdir'], unicode): subdir = item['subdir'].encode('utf-8') else: subdir = item['subdir'] if prefix: subdir = subdir[len(prefix):] body += ' <tr class="item subdir">\n' \ ' <td class="colname"><a href="%s">%s</a></td>\n' \ ' <td class="colsize"> </td>\n' \ ' <td class="coldate"> </td>\n' \ ' </tr>\n' % \ (quote(subdir), cgi.escape(subdir)) for item in listing: if 'name' in item: if isinstance(item['name'], unicode): name = item['name'].encode('utf-8') else: name = item['name'] if prefix: name = name[len(prefix):] body += ' <tr class="item %s">\n' \ ' <td class="colname"><a href="%s">%s</a></td>\n' \ ' <td class="colsize">%s</td>\n' \ ' <td class="coldate">%s</td>\n' \ ' </tr>\n' % \ (' '.join('type-' + cgi.escape(t.lower(), quote=True) for t in item['content_type'].split('/')), quote(name), cgi.escape(name), human_readable(item['bytes']), cgi.escape(item['last_modified']).split('.')[0]. replace('T', ' ')) body += ' </table>\n' \ ' </body>\n' \ '</html>\n' resp = Response(headers=headers, body=body) return resp(env, start_response)
def _perform_subrequest(self, orig_env, attributes, fp, keys): """ Performs the subrequest and returns the response. :param orig_env: The WSGI environment dict; will only be used to form a new env for the subrequest. :param attributes: dict of the attributes of the form so far. :param fp: The file-like object containing the request body. :param keys: The account keys to validate the signature with. :returns: (status_line, headers_list) """ if not keys: raise FormUnauthorized('invalid signature') try: max_file_size = int(attributes.get('max_file_size') or 0) except ValueError: raise FormInvalid('max_file_size not an integer') subenv = make_pre_authed_env(orig_env, 'PUT', agent=None, swift_source='FP') if 'QUERY_STRING' in subenv: del subenv['QUERY_STRING'] subenv['HTTP_TRANSFER_ENCODING'] = 'chunked' subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size) if not subenv['PATH_INFO'].endswith('/') and \ subenv['PATH_INFO'].count('/') < 4: subenv['PATH_INFO'] += '/' subenv['PATH_INFO'] += str_to_wsgi( attributes['filename'] or 'filename') if 'x_delete_at' in attributes: try: subenv['HTTP_X_DELETE_AT'] = int(attributes['x_delete_at']) except ValueError: raise FormInvalid('x_delete_at not an integer: ' 'Unix timestamp required.') if 'x_delete_after' in attributes: try: subenv['HTTP_X_DELETE_AFTER'] = int( attributes['x_delete_after']) except ValueError: raise FormInvalid('x_delete_after not an integer: ' 'Number of seconds required.') if 'content-type' in attributes: subenv['CONTENT_TYPE'] = \ attributes['content-type'] or 'application/octet-stream' if 'content-encoding' in attributes: subenv['HTTP_CONTENT_ENCODING'] = attributes['content-encoding'] try: if int(attributes.get('expires') or 0) < time(): raise FormUnauthorized('form expired') except ValueError: raise FormInvalid('expired not an integer') hmac_body = '%s\n%s\n%s\n%s\n%s' % ( wsgi_to_str(orig_env['PATH_INFO']), attributes.get('redirect') or '', attributes.get('max_file_size') or '0', attributes.get('max_file_count') or '0', attributes.get('expires') or '0') if six.PY3: hmac_body = hmac_body.encode('utf-8') has_valid_sig = False for key in keys: # Encode key like in swift.common.utls.get_hmac. if not isinstance(key, six.binary_type): key = key.encode('utf8') sig = hmac.new(key, hmac_body, sha1).hexdigest() if streq_const_time(sig, (attributes.get('signature') or 'invalid')): has_valid_sig = True if not has_valid_sig: raise FormUnauthorized('invalid signature') substatus = [None] subheaders = [None] wsgi_input = subenv['wsgi.input'] def _start_response(status, headers, exc_info=None): if wsgi_input.file_size_exceeded: raise EOFError("max_file_size exceeded") substatus[0] = status subheaders[0] = headers # reiterate to ensure the response started, # but drop any data on the floor close_if_possible(reiterate(self.app(subenv, _start_response))) return substatus[0], subheaders[0]
def _listing(self, env, start_response, prefix=None): """ Sends an HTML object listing to the remote client. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. :param prefix: Any prefix desired for the container listing. """ if not config_true_value(self._listings): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) tmp_env = make_pre_authed_env( env, 'GET', '/%s/%s/%s' % (self.version, self.account, self.container), self.agent, swift_source='SW') tmp_env['QUERY_STRING'] = 'delimiter=/&format=json' if prefix: tmp_env['QUERY_STRING'] += '&prefix=%s' % quote(prefix) else: prefix = '' resp = self._app_call(tmp_env) if not is_success(self._get_status_int()): return self._error_response(resp, env, start_response) listing = None body = ''.join(resp) if body: listing = json.loads(body) if not listing: resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) headers = {'Content-Type': 'text/html; charset=UTF-8'} body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 ' \ 'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' \ '<html>\n' \ ' <head>\n' \ ' <title>Listing of %s</title>\n' % \ cgi.escape(env['PATH_INFO']) if self._listings_css: body += ' <link rel="stylesheet" type="text/css" ' \ 'href="%s" />\n' % (self._build_css_path(prefix)) else: body += ' <style type="text/css">\n' \ ' h1 {font-size: 1em; font-weight: bold;}\n' \ ' th {text-align: left; padding: 0px 1em 0px 1em;}\n' \ ' td {padding: 0px 1em 0px 1em;}\n' \ ' a {text-decoration: none;}\n' \ ' </style>\n' body += ' </head>\n' \ ' <body>\n' \ ' <h1 id="title">Listing of %s</h1>\n' \ ' <table id="listing">\n' \ ' <tr id="heading">\n' \ ' <th class="colname">Name</th>\n' \ ' <th class="colsize">Size</th>\n' \ ' <th class="coldate">Date</th>\n' \ ' </tr>\n' % \ cgi.escape(env['PATH_INFO']) if prefix: body += ' <tr id="parent" class="item">\n' \ ' <td class="colname"><a href="../">../</a></td>\n' \ ' <td class="colsize"> </td>\n' \ ' <td class="coldate"> </td>\n' \ ' </tr>\n' for item in listing: if 'subdir' in item: subdir = item['subdir'] if prefix: subdir = subdir[len(prefix):] body += ' <tr class="item subdir">\n' \ ' <td class="colname"><a href="%s">%s</a></td>\n' \ ' <td class="colsize"> </td>\n' \ ' <td class="coldate"> </td>\n' \ ' </tr>\n' % \ (quote(subdir), cgi.escape(subdir)) for item in listing: if 'name' in item: name = item['name'] if prefix: name = name[len(prefix):] body += ' <tr class="item %s">\n' \ ' <td class="colname"><a href="%s">%s</a></td>\n' \ ' <td class="colsize">%s</td>\n' \ ' <td class="coldate">%s</td>\n' \ ' </tr>\n' % \ (' '.join('type-' + cgi.escape(t.lower(), quote=True) for t in item['content_type'].split('/')), quote(name), cgi.escape(name), human_readable(item['bytes']), cgi.escape(item['last_modified']).split('.')[0]. replace('T', ' ')) body += ' </table>\n' \ ' </body>\n' \ '</html>\n' resp = Response(headers=headers, body=body) return resp(env, start_response)
def handle_object(self, env, start_response): """ Handles a possible static web request for an object. This object could resolve into an index or listing request. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. """ tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') tmp_env['swift.source'] = 'SW' resp = self._app_call(tmp_env) status_int = self._get_status_int() self._get_container_info(env) if is_success(status_int) or is_redirection(status_int): # Treat directory marker objects as not found if not self._dir_type: self._dir_type = 'application/directory' content_length = self._response_header_value('content-length') content_length = int(content_length) if content_length else 0 if self._response_header_value('content-type') == self._dir_type \ and content_length <= 1: status_int = HTTP_NOT_FOUND else: start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int != HTTP_NOT_FOUND: # Retaining the previous code's behavior of not using custom error # pages for non-404 errors. self._error = None return self._error_response(resp, env, start_response) if not self._listings and not self._index: start_response(self._response_status, self._response_headers, self._response_exc_info) return resp status_int = HTTP_NOT_FOUND if self._index: tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') tmp_env['swift.source'] = 'SW' if tmp_env['PATH_INFO'][-1] != '/': tmp_env['PATH_INFO'] += '/' tmp_env['PATH_INFO'] += self._index resp = self._app_call(tmp_env) status_int = self._get_status_int() if is_success(status_int) or is_redirection(status_int): if env['PATH_INFO'][-1] != '/': resp = HTTPMovedPermanently(location=env['PATH_INFO'] + '/') return resp(env, start_response) start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int == HTTP_NOT_FOUND: if env['PATH_INFO'][-1] != '/': tmp_env = make_pre_authed_env( env, 'GET', '/%s/%s/%s' % (self.version, self.account, self.container), self.agent, swift_source='SW') tmp_env['QUERY_STRING'] = 'limit=1&format=json&delimiter' \ '=/&limit=1&prefix=%s' % quote(self.obj + '/') resp = self._app_call(tmp_env) body = ''.join(resp) if not is_success(self._get_status_int()) or not body or \ not json.loads(body): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) resp = HTTPMovedPermanently(location=env['PATH_INFO'] + '/') return resp(env, start_response) return self._listing(env, start_response, self.obj)
def _perform_subrequest(self, orig_env, attributes, fp, keys): """ Performs the subrequest and returns the response. :param orig_env: The WSGI environment dict; will only be used to form a new env for the subrequest. :param attributes: dict of the attributes of the form so far. :param fp: The file-like object containing the request body. :param keys: The account keys to validate the signature with. :returns: (status_line, headers_list) """ if not keys: raise FormUnauthorized('invalid signature') try: max_file_size = int(attributes.get('max_file_size') or 0) except ValueError: raise FormInvalid('max_file_size not an integer') subenv = make_pre_authed_env(orig_env, 'PUT', agent=None, swift_source='FP') if 'QUERY_STRING' in subenv: del subenv['QUERY_STRING'] subenv['HTTP_TRANSFER_ENCODING'] = 'chunked' subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size) if not subenv['PATH_INFO'].endswith('/') and \ subenv['PATH_INFO'].count('/') < 4: subenv['PATH_INFO'] += '/' subenv['PATH_INFO'] += attributes['filename'] or 'filename' if 'x_delete_at' in attributes: try: subenv['HTTP_X_DELETE_AT'] = int(attributes['x_delete_at']) except ValueError: raise FormInvalid('x_delete_at not an integer: ' 'Unix timestamp required.') if 'x_delete_after' in attributes: try: subenv['HTTP_X_DELETE_AFTER'] = int( attributes['x_delete_after']) except ValueError: raise FormInvalid('x_delete_after not an integer: ' 'Number of seconds required.') if 'content-type' in attributes: subenv['CONTENT_TYPE'] = \ attributes['content-type'] or 'application/octet-stream' if 'content-encoding' in attributes: subenv['HTTP_CONTENT_ENCODING'] = attributes['content-encoding'] try: if int(attributes.get('expires') or 0) < time(): raise FormUnauthorized('form expired') except ValueError: raise FormInvalid('expired not an integer') hmac_body = '%s\n%s\n%s\n%s\n%s' % ( orig_env['PATH_INFO'], attributes.get('redirect') or '', attributes.get('max_file_size') or '0', attributes.get('max_file_count') or '0', attributes.get('expires') or '0') if six.PY3: hmac_body = hmac_body.encode('utf-8') has_valid_sig = False for key in keys: sig = hmac.new(key, hmac_body, sha1).hexdigest() if streq_const_time(sig, (attributes.get('signature') or 'invalid')): has_valid_sig = True if not has_valid_sig: raise FormUnauthorized('invalid signature') substatus = [None] subheaders = [None] wsgi_input = subenv['wsgi.input'] def _start_response(status, headers, exc_info=None): if wsgi_input.file_size_exceeded: raise EOFError("max_file_size exceeded") substatus[0] = status subheaders[0] = headers # reiterate to ensure the response started, # but drop any data on the floor close_if_possible(reiterate(self.app(subenv, _start_response))) return substatus[0], subheaders[0]
def handle_object(self, env, start_response): """ Handles a possible static web request for an object. This object could resolve into an index or listing request. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. """ tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') tmp_env['swift.source'] = 'SW' resp = self._app_call(tmp_env) status_int = self._get_status_int() self._get_container_info(env) if is_success(status_int) or is_redirection(status_int): # Treat directory marker objects as not found if not self._dir_type: self._dir_type = 'application/directory' content_length = self._response_header_value('content-length') content_length = int(content_length) if content_length else 0 if self._response_header_value('content-type') == self._dir_type \ and content_length <= 1: status_int = HTTP_NOT_FOUND else: start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int != HTTP_NOT_FOUND: # Retaining the previous code's behavior of not using custom error # pages for non-404 errors. self._error = None return self._error_response(resp, env, start_response) if not self._listings and not self._index: return self.app(env, start_response) status_int = HTTP_NOT_FOUND if self._index: tmp_env = dict(env) tmp_env['HTTP_USER_AGENT'] = \ '%s StaticWeb' % env.get('HTTP_USER_AGENT') tmp_env['swift.source'] = 'SW' if tmp_env['PATH_INFO'][-1] != '/': tmp_env['PATH_INFO'] += '/' tmp_env['PATH_INFO'] += self._index resp = self._app_call(tmp_env) status_int = self._get_status_int() if is_success(status_int) or is_redirection(status_int): if env['PATH_INFO'][-1] != '/': resp = HTTPMovedPermanently( location=env['PATH_INFO'] + '/') return resp(env, start_response) start_response(self._response_status, self._response_headers, self._response_exc_info) return resp if status_int == HTTP_NOT_FOUND: if env['PATH_INFO'][-1] != '/': tmp_env = make_pre_authed_env( env, 'GET', '/%s/%s/%s' % ( self.version, self.account, self.container), self.agent, swift_source='SW') tmp_env['QUERY_STRING'] = 'limit=1&format=json&delimiter' \ '=/&limit=1&prefix=%s' % quote(self.obj + '/') resp = self._app_call(tmp_env) body = ''.join(resp) if not is_success(self._get_status_int()) or not body or \ not json.loads(body): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) resp = HTTPMovedPermanently(location=env['PATH_INFO'] + '/') return resp(env, start_response) return self._listing(env, start_response, self.obj)
def test_pre_auth_copies_script_name_unless_path_overridden(self): e = wsgi.make_pre_authed_env({"SCRIPT_NAME": "/script_name"}, path="/override") self.assertEquals(e["SCRIPT_NAME"], "") self.assertEquals(e["PATH_INFO"], "/override")
def _perform_subrequest(self, orig_env, attributes, fp, keys): """ Performs the subrequest and returns the response. :param orig_env: The WSGI environment dict; will only be used to form a new env for the subrequest. :param attributes: dict of the attributes of the form so far. :param fp: The file-like object containing the request body. :param keys: The account keys to validate the signature with. :returns: (status_line, headers_list, message) """ if not keys: raise FormUnauthorized("invalid signature") try: max_file_size = int(attributes.get("max_file_size") or 0) except ValueError: raise FormInvalid("max_file_size not an integer") subenv = make_pre_authed_env(orig_env, "PUT", agent=None, swift_source="FP") if "QUERY_STRING" in subenv: del subenv["QUERY_STRING"] subenv["HTTP_TRANSFER_ENCODING"] = "chunked" subenv["wsgi.input"] = _CappedFileLikeObject(fp, max_file_size) if subenv["PATH_INFO"][-1] != "/" and subenv["PATH_INFO"].count("/") < 4: subenv["PATH_INFO"] += "/" subenv["PATH_INFO"] += attributes["filename"] or "filename" if "x_delete_at" in attributes: try: subenv["HTTP_X_DELETE_AT"] = int(attributes["x_delete_at"]) except ValueError: raise FormInvalid("x_delete_at not an integer: " "Unix timestamp required.") if "x_delete_after" in attributes: try: subenv["HTTP_X_DELETE_AFTER"] = int(attributes["x_delete_after"]) except ValueError: raise FormInvalid("x_delete_after not an integer: " "Number of seconds required.") if "content-type" in attributes: subenv["CONTENT_TYPE"] = attributes["content-type"] or "application/octet-stream" elif "CONTENT_TYPE" in subenv: del subenv["CONTENT_TYPE"] try: if int(attributes.get("expires") or 0) < time(): raise FormUnauthorized("form expired") except ValueError: raise FormInvalid("expired not an integer") hmac_body = "%s\n%s\n%s\n%s\n%s" % ( orig_env["PATH_INFO"], attributes.get("redirect") or "", attributes.get("max_file_size") or "0", attributes.get("max_file_count") or "0", attributes.get("expires") or "0", ) has_valid_sig = False for key in keys: sig = hmac.new(key, hmac_body, sha1).hexdigest() if streq_const_time(sig, (attributes.get("signature") or "invalid")): has_valid_sig = True if not has_valid_sig: raise FormUnauthorized("invalid signature") substatus = [None] subheaders = [None] wsgi_input = subenv["wsgi.input"] def _start_response(status, headers, exc_info=None): if wsgi_input.file_size_exceeded: raise EOFError("max_file_size exceeded") substatus[0] = status subheaders[0] = headers i = iter(self.app(subenv, _start_response)) try: i.next() except StopIteration: pass return substatus[0], subheaders[0], ""
def test_pre_auth_copies_script_name(self): e = wsgi.make_pre_authed_env({'SCRIPT_NAME': '/script_name'}) self.assertEquals(e['SCRIPT_NAME'], '/script_name')
def _listing(self, env, start_response, prefix=None): """ Sends an HTML object listing to the remote client. :param env: The original WSGI environment dict. :param start_response: The original WSGI start_response hook. :param prefix: Any prefix desired for the container listing. """ if not config_true_value(self._listings): resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) tmp_env = make_pre_authed_env( env, "GET", "/%s/%s/%s" % (self.version, self.account, self.container), self.agent, swift_source="SW" ) tmp_env["QUERY_STRING"] = "delimiter=/&format=json" if prefix: tmp_env["QUERY_STRING"] += "&prefix=%s" % quote(prefix) else: prefix = "" resp = self._app_call(tmp_env) if not is_success(self._get_status_int()): return self._error_response(resp, env, start_response) listing = None body = "".join(resp) if body: listing = json.loads(body) if not listing: resp = HTTPNotFound()(env, self._start_response) return self._error_response(resp, env, start_response) headers = {"Content-Type": "text/html; charset=UTF-8"} body = ( '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 ' 'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' "<html>\n" " <head>\n" " <title>Listing of %s</title>\n" % cgi.escape(env["PATH_INFO"]) ) if self._listings_css: body += ' <link rel="stylesheet" type="text/css" ' 'href="%s" />\n' % (self._build_css_path(prefix)) else: body += ( ' <style type="text/css">\n' " h1 {font-size: 1em; font-weight: bold;}\n" " th {text-align: left; padding: 0px 1em 0px 1em;}\n" " td {padding: 0px 1em 0px 1em;}\n" " a {text-decoration: none;}\n" " </style>\n" ) body += ( " </head>\n" " <body>\n" ' <h1 id="title">Listing of %s</h1>\n' ' <table id="listing">\n' ' <tr id="heading">\n' ' <th class="colname">Name</th>\n' ' <th class="colsize">Size</th>\n' ' <th class="coldate">Date</th>\n' " </tr>\n" % cgi.escape(env["PATH_INFO"]) ) if prefix: body += ( ' <tr id="parent" class="item">\n' ' <td class="colname"><a href="../">../</a></td>\n' ' <td class="colsize"> </td>\n' ' <td class="coldate"> </td>\n' " </tr>\n" ) for item in listing: if "subdir" in item: if isinstance(item["subdir"], unicode): subdir = item["subdir"].encode("utf-8") else: subdir = item["subdir"] if prefix: subdir = subdir[len(prefix) :] body += ( ' <tr class="item subdir">\n' ' <td class="colname"><a href="%s">%s</a></td>\n' ' <td class="colsize"> </td>\n' ' <td class="coldate"> </td>\n' " </tr>\n" % (quote(subdir), cgi.escape(subdir)) ) for item in listing: if "name" in item: if isinstance(item["name"], unicode): name = item["name"].encode("utf-8") else: name = item["name"] if prefix: name = name[len(prefix) :] body += ( ' <tr class="item %s">\n' ' <td class="colname"><a href="%s">%s</a></td>\n' ' <td class="colsize">%s</td>\n' ' <td class="coldate">%s</td>\n' " </tr>\n" % ( " ".join("type-" + cgi.escape(t.lower(), quote=True) for t in item["content_type"].split("/")), quote(name), cgi.escape(name), human_readable(item["bytes"]), cgi.escape(item["last_modified"]).split(".")[0].replace("T", " "), ) ) body += " </table>\n" " </body>\n" "</html>\n" resp = Response(headers=headers, body=body) return resp(env, start_response)
def test_pre_auth_creates_script_name(self): e = wsgi.make_pre_authed_env({}) self.assertTrue('SCRIPT_NAME' in e)
def test_pre_auth_copies_script_name(self): e = wsgi.make_pre_authed_env({"SCRIPT_NAME": "/script_name"}) self.assertEquals(e["SCRIPT_NAME"], "/script_name")
def test_pre_auth_copies_script_name_unless_path_overridden(self): e = wsgi.make_pre_authed_env({'SCRIPT_NAME': '/script_name'}, path='/override') self.assertEquals(e['SCRIPT_NAME'], '') self.assertEquals(e['PATH_INFO'], '/override')
def _perform_subrequest(self, orig_env, attributes, fp, keys): """ Performs the subrequest and returns the response. :param orig_env: The WSGI environment dict; will only be used to form a new env for the subrequest. :param attributes: dict of the attributes of the form so far. :param fp: The file-like object containing the request body. :param keys: The account keys to validate the signature with. :returns: (status_line, headers_list, message) """ if not keys: raise FormUnauthorized('invalid signature') try: max_file_size = int(attributes.get('max_file_size') or 0) except ValueError: raise FormInvalid('max_file_size not an integer') subenv = make_pre_authed_env(orig_env, 'PUT', agent=None, swift_source='FP') if 'QUERY_STRING' in subenv: del subenv['QUERY_STRING'] subenv['HTTP_TRANSFER_ENCODING'] = 'chunked' subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size) if subenv['PATH_INFO'][-1] != '/' and \ subenv['PATH_INFO'].count('/') < 4: subenv['PATH_INFO'] += '/' subenv['PATH_INFO'] += attributes['filename'] or 'filename' if 'x_delete_at' in attributes: try: subenv['HTTP_X_DELETE_AT'] = int(attributes['x_delete_at']) except ValueError: raise FormInvalid('x_delete_at not an integer: ' 'Unix timestamp required.') if 'x_delete_after' in attributes: try: subenv['HTTP_X_DELETE_AFTER'] = int( attributes['x_delete_after']) except ValueError: raise FormInvalid('x_delete_after not an integer: ' 'Number of seconds required.') if 'content-type' in attributes: subenv['CONTENT_TYPE'] = \ attributes['content-type'] or 'application/octet-stream' elif 'CONTENT_TYPE' in subenv: del subenv['CONTENT_TYPE'] try: if int(attributes.get('expires') or 0) < time(): raise FormUnauthorized('form expired') except ValueError: raise FormInvalid('expired not an integer') hmac_body = '%s\n%s\n%s\n%s\n%s' % ( orig_env['PATH_INFO'], attributes.get('redirect') or '', attributes.get('max_file_size') or '0', attributes.get('max_file_count') or '0', attributes.get('expires') or '0') has_valid_sig = False for key in keys: sig = hmac.new(key, hmac_body, sha1).hexdigest() if streq_const_time(sig, (attributes.get('signature') or 'invalid')): has_valid_sig = True if not has_valid_sig: raise FormUnauthorized('invalid signature') substatus = [None] subheaders = [None] wsgi_input = subenv['wsgi.input'] def _start_response(status, headers, exc_info=None): if wsgi_input.file_size_exceeded: raise EOFError("max_file_size exceeded") substatus[0] = status subheaders[0] = headers i = iter(self.app(subenv, _start_response)) try: next(i) except StopIteration: pass return substatus[0], subheaders[0], ''