def _request_uri(environ): """Like wsgiref.url.request_uri, except eliminates :80 ports Return the full request URI""" url = environ["wsgi.url_scheme"] + "://" if environ.get("HTTP_HOST"): url += environ["HTTP_HOST"] else: url += environ["SERVER_NAME"] + ":" + environ["SERVER_PORT"] if url.endswith(":80") and environ["wsgi.url_scheme"] == "http": url = url[:-3] elif url.endswith(":443") and environ["wsgi.url_scheme"] == "https": url = url[:-4] if PY3: # pragma: no cover script_name = bytes_(environ.get("SCRIPT_NAME", "/"), "latin-1") path_info = bytes_(environ.get("PATH_INFO", ""), "latin-1") else: script_name = environ.get("SCRIPT_NAME", "/") path_info = environ.get("PATH_INFO", "") url += url_quote(script_name) qpath_info = url_quote(path_info) if not "SCRIPT_NAME" in environ: url += qpath_info[1:] else: url += qpath_info return url
def set_cookie(self, key, value='', max_age=None, path='/', domain=None, secure=False, httponly=False, comment=None, expires=None, overwrite=False): """ Set (add) a cookie for the response """ if overwrite: self.unset_cookie(key, strict=False) if value is None: # delete the cookie from the client value = '' max_age = 0 expires = timedelta(days=-5) elif expires is None and max_age is not None: if isinstance(max_age, int): max_age = timedelta(seconds=max_age) expires = datetime.utcnow() + max_age elif max_age is None and expires is not None: max_age = expires - datetime.utcnow() value = bytes_(value, 'utf8') key = bytes_(key, 'utf8') m = Morsel(key, value) m.path = bytes_(path, 'utf8') m.domain = bytes_(domain, 'utf8') m.comment = bytes_(comment, 'utf8') m.expires = expires m.max_age = max_age m.secure = secure m.httponly = httponly self.headerlist.append(('Set-Cookie', m.serialize()))
def make_cookie(name, value, max_age=None, path='/', domain=None, secure=False, httponly=False, comment=None): """ Generate a cookie value. If ``value`` is None, generate a cookie value with an expiration date in the past""" # We are deleting the cookie, override max_age and expires if value is None: value = b'' max_age = 0 expires = -5 * 60 * 60 * 24 # Convert max_age to seconds elif isinstance(max_age, timedelta): max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds expires = max_age else: expires = max_age morsel = Morsel(name, value) if domain is not None: morsel.domain = bytes_(domain) if path is not None: morsel.path = bytes_(path) if httponly: morsel.httponly = True if secure: morsel.secure = True if max_age is not None: morsel.max_age = max_age if expires is not None: morsel.expires = expires if comment is not None: morsel.comment = bytes_(comment) return morsel.serialize()
def test_index_page(self): os.mkdir(os.path.join(self.test_dir, "index-test")) create_file(bytes_("index"), self.test_dir, "index-test", "index.html") app = static.DirectoryApp(self.test_dir) resp = get_response(app, "/index-test") self.assertEqual(resp.status_code, 301) self.assertTrue(resp.location.endswith("/index-test/")) resp = get_response(app, "/index-test?test") self.assertTrue(resp.location.endswith("/index-test/?test")) resp = get_response(app, "/index-test/") self.assertEqual(resp.status_code, 200) self.assertEqual(resp.body, bytes_("index")) self.assertEqual(resp.content_type, "text/html") resp = get_response(app, "/index-test/index.html") self.assertEqual(resp.status_code, 200) self.assertEqual(resp.body, bytes_("index")) redir_app = static.DirectoryApp(self.test_dir, hide_index_with_redirect=True) resp = get_response(redir_app, "/index-test/index.html") self.assertEqual(resp.status_code, 301) self.assertTrue(resp.location.endswith("/index-test/")) resp = get_response(redir_app, "/index-test/index.html?test") self.assertTrue(resp.location.endswith("/index-test/?test")) page_app = static.DirectoryApp(self.test_dir, index_page="something-else.html") self.assertEqual( get_response(page_app, "/index-test/").status_code, 404)
def _request_uri(environ): """Like wsgiref.url.request_uri, except eliminates :80 ports Return the full request URI""" url = environ['wsgi.url_scheme'] + '://' if environ.get('HTTP_HOST'): url += environ['HTTP_HOST'] else: url += environ['SERVER_NAME'] + ':' + environ['SERVER_PORT'] if url.endswith(':80') and environ['wsgi.url_scheme'] == 'http': url = url[:-3] elif url.endswith(':443') and environ['wsgi.url_scheme'] == 'https': url = url[:-4] if PY3: # pragma: no cover script_name = bytes_(environ.get('SCRIPT_NAME', '/'), 'latin-1') path_info = bytes_(environ.get('PATH_INFO', ''), 'latin-1') else: script_name = environ.get('SCRIPT_NAME', '/') path_info = environ.get('PATH_INFO', '') url += url_quote(script_name) qpath_info = url_quote(path_info) if not 'SCRIPT_NAME' in environ: url += qpath_info[1:] else: url += qpath_info return url
def _request_uri(environ): """Like wsgiref.url.request_uri, except eliminates :80 ports Return the full request URI""" url = environ['wsgi.url_scheme']+'://' if environ.get('HTTP_HOST'): url += environ['HTTP_HOST'] else: url += environ['SERVER_NAME'] + ':' + environ['SERVER_PORT'] if url.endswith(':80') and environ['wsgi.url_scheme'] == 'http': url = url[:-3] elif url.endswith(':443') and environ['wsgi.url_scheme'] == 'https': url = url[:-4] if PY3: # pragma: no cover script_name = bytes_(environ.get('SCRIPT_NAME', '/'), 'latin-1') path_info = bytes_(environ.get('PATH_INFO', ''), 'latin-1') else: script_name = environ.get('SCRIPT_NAME', '/') path_info = environ.get('PATH_INFO', '') url += url_quote(script_name) qpath_info = url_quote(path_info) if not 'SCRIPT_NAME' in environ: url += qpath_info[1:] else: url += qpath_info return url
def __init__( self, secret, salt, hashalg='sha512', serializer=None, ): self.salt = salt self.secret = secret self.hashalg = hashalg try: # bwcompat with webob <= 1.3.1, leave latin-1 as the default self.salted_secret = bytes_(salt or '') + bytes_(secret) except UnicodeEncodeError: self.salted_secret = (bytes_(salt or '', 'utf-8') + bytes_(secret, 'utf-8')) self.digestmod = lambda string=b'': hashlib.new(self.hashalg, string) self.digest_size = self.digestmod().digest_size if serializer is None: serializer = JSONSerializer() self.serializer = serializer
def test_seek_bigger_than_limit(self): fp = BytesIO(bytes_("0123456789")) i = static.FileIter(fp).app_iter_range(limit=1, seek=2) # XXX: this should not return anything actually, since we are starting # to read after the place we wanted to stop. self.assertEqual(bytes_("23456789"), next(i)) self.assertRaises(StopIteration, next, i)
def test_multiple_reads(self): fp = BytesIO(bytes_("012")) i = static.FileIter(fp).app_iter_range(block_size=1) self.assertEqual(bytes_("0"), next(i)) self.assertEqual(bytes_("1"), next(i)) self.assertEqual(bytes_("2"), next(i)) self.assertRaises(StopIteration, next, i)
def serialize(secret, salt, data): import hmac import base64 import json from hashlib import sha1 from webob.compat import bytes_ salted_secret = bytes_(salt or '', 'utf-8') + bytes_(secret, 'utf-8') cstruct = bytes_(json.dumps(data)) sig = hmac.new(salted_secret, cstruct, sha1).digest() return base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=')
def make_cookie(name, value, max_age=None, path='/', domain=None, secure=False, httponly=False, comment=None): """ Generate a cookie value. If ``value`` is None, generate a cookie value with an expiration date in the past""" # We are deleting the cookie, override max_age and expires if value is None: value = b'' # Note that the max-age value of zero is technically contraspec; # RFC6265 says that max-age cannot be zero. However, all browsers # appear to support this to mean "delete immediately". # http://www.timwilson.id.au/news-three-critical-problems-with-rfc6265.html max_age = 0 expires = 'Wed, 31-Dec-97 23:59:59 GMT' # Convert max_age to seconds elif isinstance(max_age, timedelta): max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds expires = max_age elif max_age is not None: try: max_age = int(max_age) except ValueError: raise ValueError( 'max_age should be an integer. Amount of seconds until expiration.' ) expires = max_age else: expires = None morsel = Morsel(name, value) if domain is not None: morsel.domain = bytes_(domain) if path is not None: morsel.path = bytes_(path) if httponly: morsel.httponly = True if secure: morsel.secure = True if max_age is not None: morsel.max_age = max_age if expires is not None: morsel.expires = expires if comment is not None: morsel.comment = bytes_(comment) return morsel.serialize()
def create_file(content, *paths): """Convenient function to create a new file with some content""" path = os.path.join(*paths) with open(path, "wb") as fp: fp.write(bytes_(content)) return path
def serialize_samesite(v): v = bytes_(v) if v.lower() not in (b"strict", b"lax"): raise ValueError("SameSite must be 'Strict' or 'Lax'") return v
def conditional_response_app(self, environ, start_response): """ Like the normal __call__ interface, but checks conditional headers: * If-Modified-Since (304 Not Modified; only on GET, HEAD) * If-None-Match (304 Not Modified; only on GET, HEAD) * Range (406 Partial Content; only on GET, HEAD) """ req = BaseRequest(environ) headerlist = self._abs_headerlist(environ) method = environ.get("REQUEST_METHOD", "GET") if method in self._safe_methods: status304 = False if req.if_none_match and self.etag: status304 = self.etag in req.if_none_match elif req.if_modified_since and self.last_modified: status304 = self.last_modified <= req.if_modified_since if status304: start_response("304 Not Modified", filter_headers(headerlist)) return EmptyResponse(self._app_iter) if ( req.range and self in req.if_range and self.content_range is None and method in ("HEAD", "GET") and self.status_code == 200 and self.content_length is not None ): content_range = req.range.content_range(self.content_length) if content_range is None: iter_close(self._app_iter) body = bytes_("Requested range not satisfiable: %s" % req.range) headerlist = [ ("Content-Length", str(len(body))), ("Content-Range", str(ContentRange(None, None, self.content_length))), ("Content-Type", "text/plain"), ] + filter_headers(headerlist) start_response("416 Requested Range Not Satisfiable", headerlist) if method == "HEAD": return () return [body] else: app_iter = self.app_iter_range(content_range.start, content_range.stop) if app_iter is not None: # the following should be guaranteed by # Range.range_for_length(length) assert content_range.start is not None headerlist = [ ("Content-Length", str(content_range.stop - content_range.start)), ("Content-Range", str(content_range)), ] + filter_headers(headerlist, ("content-length",)) start_response("206 Partial Content", headerlist) if method == "HEAD": return EmptyResponse(app_iter) return app_iter start_response(self.status, headerlist) if method == "HEAD": return EmptyResponse(self._app_iter) return self._app_iter
def test_disconnect_detection_hinted_readline(): data = 'abc'*(1<<20) req = Request.blank('/', POST=data) req.is_body_seekable = False line = req.body_file.readline(1<<16) assert line assert bytes_(data).startswith(line)
def serialize_max_age(v): if isinstance(v, timedelta): v = str(v.seconds + v.days * 24 * 60 * 60) elif isinstance(v, int): v = str(v) return bytes_(v)
def serialize_samesite(v): v = bytes_(v) if v.lower() not in (b"strict", b"lax", b"none"): raise ValueError("SameSite must be 'strict', 'lax', or 'none'") return v
def conditional_response_app(self, environ, start_response): """ Like the normal __call__ interface, but checks conditional headers: * If-Modified-Since (304 Not Modified; only on GET, HEAD) * If-None-Match (304 Not Modified; only on GET, HEAD) * Range (406 Partial Content; only on GET, HEAD) """ req = BaseRequest(environ) headerlist = self._abs_headerlist(environ) method = environ.get('REQUEST_METHOD', 'GET') if method in self._safe_methods: status304 = False if req.if_none_match and self.etag: status304 = self.etag in req.if_none_match elif req.if_modified_since and self.last_modified: status304 = self.last_modified <= req.if_modified_since if status304: start_response('304 Not Modified', filter_headers(headerlist)) return EmptyResponse(self._app_iter) if (req.range and self in req.if_range and self.content_range is None and method in ('HEAD', 'GET') and self.status_code == 200 and self.content_length is not None): content_range = req.range.content_range(self.content_length) if content_range is None: iter_close(self._app_iter) body = bytes_("Requested range not satisfiable: %s" % req.range) headerlist = [ ('Content-Length', str(len(body))), ('Content-Range', str(ContentRange(None, None, self.content_length))), ('Content-Type', 'text/plain'), ] + filter_headers(headerlist) start_response('416 Requested Range Not Satisfiable', headerlist) if method == 'HEAD': return () return [body] else: app_iter = self.app_iter_range(content_range.start, content_range.stop) if app_iter is not None: # the following should be guaranteed by # Range.range_for_length(length) assert content_range.start is not None headerlist = [ ('Content-Length', str(content_range.stop - content_range.start)), ('Content-Range', str(content_range)), ] + filter_headers(headerlist, ('content-length', )) start_response('206 Partial Content', headerlist) if method == 'HEAD': return EmptyResponse(app_iter) return app_iter start_response(self.status, headerlist) if method == 'HEAD': return EmptyResponse(self._app_iter) return self._app_iter
def dispatch_wsgi(self, environ, start_response): """ Dispatches a WSGI request. :param environ: WSGI request environment. :param start_response: WSGI response callback :returns: A valid WSGI response content. """ req = Request(environ) req.response = Response() try: resp = self.dispatch_request(req) except HTTPException as e: resp = e if inspect.isgenerator(resp): resp = Response(app_iter=resp) elif resp is None: resp = req.response if isinstance(resp, text_type): resp = bytes_(resp, req.charset) if isinstance(resp, bytes): body = resp resp = req.response resp.write(body) if resp is not req.response: resp = req.response.merge_cookies(resp) return resp(environ, start_response)
def _send_interrupted_req(server, path='/'): sock = socket.socket() sock.connect(('localhost', server.server_port)) f = sock.makefile('wb') f.write(bytes_(_interrupted_req % path)) f.flush() f.close() sock.close()
def _send_interrupted_req(server, path="/"): sock = socket.socket() sock.connect(("localhost", server.server_port)) f = sock.makefile("wb") f.write(bytes_(_interrupted_req % path)) f.flush() f.close() sock.close()
def make_cookie(name, value, max_age=None, path='/', domain=None, secure=False, httponly=False, comment=None): """ Generate a cookie value. If ``value`` is None, generate a cookie value with an expiration date in the past""" # We are deleting the cookie, override max_age and expires if value is None: value = b'' # Note that the max-age value of zero is technically contraspec; # RFC6265 says that max-age cannot be zero. However, all browsers # appear to support this to mean "delete immediately". # http://www.timwilson.id.au/news-three-critical-problems-with-rfc6265.html max_age = 0 expires = 'Wed, 31-Dec-97 23:59:59 GMT' # Convert max_age to seconds elif isinstance(max_age, timedelta): max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds expires = max_age elif max_age is not None: try: max_age = int(max_age) except ValueError: raise ValueError('max_age should be an integer. Amount of seconds until expiration.') expires = max_age else: expires = None morsel = Morsel(name, value) if domain is not None: morsel.domain = bytes_(domain) if path is not None: morsel.path = bytes_(path) if httponly: morsel.httponly = True if secure: morsel.secure = True if max_age is not None: morsel.max_age = max_age if expires is not None: morsel.expires = expires if comment is not None: morsel.comment = bytes_(comment) return morsel.serialize()
def extract_cookie_value(response_cookies, cookie_name): """ Making sure cookie unescaped from double quotes when extracting from test response using Webob approach of cookie parsing. """ oscar_open_basket_cookie = bytes_(response_cookies[cookie_name]) oscar_open_basket_cookie = _unquote(oscar_open_basket_cookie) return oscar_open_basket_cookie.decode('utf-8')
def compute_application_url(request): virtual = request.environment.get('HTTP_X_VHM_ROOT') script_name = request.script_name if virtual is not None: script_name = script_name.lstrip(virtual) bscript_name = bytes_(script_name, request.url_encoding) # FIXME : host can be virtual too. return request.host_url + url_quote(bscript_name, PATH_SAFE)
def test_wsgify_post(self): post_dict = dict(speaker="Robin", words="Holy test coverage, Batman!") @wsgify def test_app(req): return Response("%s: %s" % (req.POST["speaker"], req.POST["words"])) resp = test_app.post("/url/path", post_dict) self.assertEqual(resp.body, bytes_("%s: %s" % (post_dict["speaker"], post_dict["words"])))
def test_serve_file(self): app = static.DirectoryApp(self.test_dir) create_file('abcde', self.test_dir, 'bar') self.assertEqual(404, get_response(app).status_code) self.assertEqual(404, get_response(app, '/foo').status_code) resp = get_response(app, '/bar') self.assertEqual(200, resp.status_code) self.assertEqual(bytes_('abcde'), resp.body)
def __init__(self, secret, salt, hashalg="sha512", serializer=None): self.salt = salt self.secret = secret self.hashalg = hashalg try: # bwcompat with webob <= 1.3.1, leave latin-1 as the default self.salted_secret = bytes_(salt or "") + bytes_(secret) except UnicodeEncodeError: self.salted_secret = bytes_(salt or "", "utf-8") + bytes_(secret, "utf-8") self.digestmod = lambda string=b"": hashlib.new(self.hashalg, string) self.digest_size = self.digestmod().digest_size if serializer is None: serializer = JSONSerializer() self.serializer = serializer
def test_serve_file(self): app = static.DirectoryApp(self.test_dir) create_file("abcde", self.test_dir, "bar") self.assertEqual(404, get_response(app).status_code) self.assertEqual(404, get_response(app, "/foo").status_code) resp = get_response(app, "/bar") self.assertEqual(200, resp.status_code) self.assertEqual(bytes_("abcde"), resp.body)
def _mutate_header(self, name, value): header = self._environ.get("HTTP_COOKIE") had_header = header is not None header = header or "" if not PY2: header = header.encode("latin-1") bytes_name = bytes_(name, "ascii") if value is None: replacement = None else: bytes_val = _value_quote(bytes_(value, "utf-8")) replacement = bytes_name + b"=" + bytes_val matches = _rx_cookie.finditer(header) found = False for match in matches: start, end = match.span() match_name = match.group(1) if match_name == bytes_name: found = True if replacement is None: # remove value header = header[:start].rstrip(b" ;") + header[end:] else: # replace value header = header[:start] + replacement + header[end:] break else: if replacement is not None: if header: header += b"; " + replacement else: header = replacement if header: self._environ["HTTP_COOKIE"] = native_(header, "latin-1") elif had_header: self._environ["HTTP_COOKIE"] = "" return found
def test_wsgify_call_args_override(self): resp_str = "args: %s, kwargs: %s" def show_vars(req, *args, **kwargs): return bytes_(resp_str % (sorted(args), sorted(kwargs.items()))) app = wsgify(show_vars, args=('foo', 'bar'), kwargs={'a': 1, 'b': 2}) resp = app(Request.blank('/'), 'qux', c=3) self.assertEqual(resp, bytes_(resp_str % ("['qux']", "[('c', 3)]")))
def test_wsgify_post(self): post_dict = dict(speaker='Robin', words='Holy test coverage, Batman!') @wsgify def test_app(req): return Response('%s: %s' % (req.POST['speaker'], req.POST['words'])) resp = test_app.post('/url/path', post_dict) self.assertEqual(resp.body, bytes_('%s: %s' % (post_dict['speaker'], post_dict['words'])))
def test_wsgify_call_args_override(self): resp_str = "args: %s, kwargs: %s" def show_vars(req, *args, **kwargs): return bytes_(resp_str % (sorted(args), sorted(kwargs.items()))) app = wsgify(show_vars, args=("foo", "bar"), kwargs={"a": 1, "b": 2}) resp = app(Request.blank("/"), "qux", c=3) self.assertEqual(resp, bytes_(resp_str % ("['qux']", "[('c', 3)]")))
def __init__(self, secret, salt, hashalg='sha512', serializer=None, ): self.salt = salt self.secret = secret self.hashalg = hashalg self.salted_secret = bytes_(salt or '') + bytes_(secret) self.digestmod = lambda string=b'': hashlib.new(self.hashalg, string) self.digest_size = self.digestmod().digest_size if serializer is None: serializer = JSONSerializer() self.serializer = serializer
def response_to_file(self, fp, block_size=1 << 16): # 64KB chunked = self.content_length is None and self.headers.get( 'Connection', '').lower() != 'close' parts = ['HTTP/1.1 ', self.status, '\r\n'] parts += map('%s: %s\r\n'.__mod__, self.headerlist) if chunked: parts += ['Transfer-Encoding: chunked\r\n'] parts += ['\r\n'] fp.write(bytes_(''.join(parts))) status_code = self.status_code if status_code in (204, 304) or status_code // 100 == 1: return if not chunked: block = bytearray() for item in self.app_iter: block.extend(bytes_(item)) if len(block) >= block_size: fp.write(block) block = bytearray() if block: fp.write(block) return chunk = bytearray() for item in self.app_iter: chunk.extend(bytes_(item)) if len(chunk) < block_size: continue fp.write(bytes_('%x\r\n' % block_size)) fp.write(chunk[:block_size]) fp.write(b'\r\n') chunk = chunk[block_size:] if chunk: fp.write(bytes_('%x\r\n' % len(chunk))) chunk.extend(b'\r\n') fp.write(chunk) fp.write(b'0\r\n\r\n')
def test_wsgify(self): resp_str = 'hey, this is a test: %s' @wsgify def test_app(req): return bytes_(resp_str % req.url) resp = self._testit(test_app, '/a url') self.assertEqual(resp.body, bytes_(resp_str % 'http://localhost/a%20url')) self.assertEqual(resp.content_length, 45) self.assertEqual(resp.content_type, 'text/html') self.assertEqual(resp.charset, 'UTF-8')
def response_to_file(self, fp, block_size=1 << 16): # 64KB chunked = self.content_length is None and self.headers.get('Connection', '').lower() != 'close' parts = ['HTTP/1.1 ', self.status, '\r\n'] parts += map('%s: %s\r\n'.__mod__, self.headerlist) if chunked: parts += ['Transfer-Encoding: chunked\r\n'] parts += ['\r\n'] fp.write(bytes_(''.join(parts))) status_code = self.status_code if status_code in (204, 304) or status_code // 100 == 1: return if not chunked: block = bytearray() for item in self.app_iter: block.extend(bytes_(item)) if len(block) >= block_size: fp.write(block) block = bytearray() if block: fp.write(block) return chunk = bytearray() for item in self.app_iter: chunk.extend(bytes_(item)) if len(chunk) < block_size: continue fp.write(bytes_('%x\r\n' % block_size)) fp.write(chunk[:block_size]) fp.write(b'\r\n') chunk = chunk[block_size:] if chunk: fp.write(bytes_('%x\r\n' % len(chunk))) chunk.extend(b'\r\n') fp.write(chunk) fp.write(b'0\r\n\r\n')
def _valid_cookie_name(self, name): if not isinstance(name, string_types): raise TypeError(name, 'cookie name must be a string') if not isinstance(name, text_type): name = text_(name, 'utf-8') try: bytes_cookie_name = bytes_(name, 'ascii') except UnicodeEncodeError: raise TypeError('cookie name must be encodable to ascii') if not _valid_cookie_name(bytes_cookie_name): raise TypeError('cookie name must be valid according to RFC 6265') return name
def test_wsgify(self): resp_str = "hey, this is a test: %s" @wsgify def test_app(req): return bytes_(resp_str % req.url) resp = self._testit(test_app, "/a url") self.assertEqual(resp.body, bytes_(resp_str % "http://localhost/a%20url")) self.assertEqual(resp.content_length, 45) self.assertEqual(resp.content_type, "text/html") self.assertEqual(resp.charset, "UTF-8")
def test_wsgify_post(self): post_dict = dict(speaker="Robin", words="Holy test coverage, Batman!") @wsgify def test_app(req): return Response("%s: %s" % (req.POST["speaker"], req.POST["words"])) resp = test_app.post("/url/path", post_dict) self.assertEqual( resp.body, bytes_("%s: %s" % (post_dict["speaker"], post_dict["words"])))
def test_wsgify_call_args(self): resp_str = "args: %s, kwargs: %s" def show_vars(req, *args, **kwargs): return bytes_(resp_str % (sorted(args), sorted(kwargs.items()))) app = wsgify(show_vars, args=("foo", "bar"), kwargs={"a": 1, "b": 2}) resp = app(Request.blank("/")) self.assertEqual( resp, bytes_(resp_str % ("['bar', 'foo']", "[('a', 1), ('b', 2)]")))
def loads(self, bstruct): """ Given a ``bstruct`` (a bytestring), verify the signature and then deserialize and return the deserialized value. A ``ValueError`` will be raised if the signature fails to validate. """ try: cstruct = base64.urlsafe_b64decode(bytes_(bstruct)) except (binascii.Error, TypeError) as e: raise ValueError('Badly formed base64 data: %s' % e) return self.serializer.loads(cstruct)