def __init__(self, response=None, status=200, charset='utf-8', content_type='text/html'): if response is None: self.response = [] elif isinstance(response, (str, bytes)): self.response = [response] else: self.response = response self.charset = charset self.headers = Headers() self.headers.add_header('content-type', f'{content_type}; charset={charset})') self._status = status
def __call__(self): """Set the headers and return the body. It is not necessary to supply Content-length, because this is added by the caller. """ headers = Headers([]) content_type_params = {} if self.charset is not None: content_type_params['charset'] = self.charset headers.add_header( 'Content-Type', self.content_type, **content_type_params) headers.add_header( 'Content-Disposition', 'attachment', filename=self.filename) for key, value in headers.items(): self.request.response.setHeader(key, value) return self.getBody()
def static_file_view(env, start_response, filename, block_size, charset): method = env['REQUEST_METHOD'].upper() if method not in ('HEAD', 'GET'): start_response('405 METHOD NOT ALLOWED', [('Content-Type', 'text/plain; UTF-8')]) return [b''] mimetype, encoding = mimetypes.guess_type(filename) headers = Headers([]) headers.add_header('Content-Encodings', encoding) headers.add_header('Content-Type', get_content_type(mimetype, charset)) headers.add_header('Content-Length', get_content_length(filename)) headers.add_header('Last-Modified', generate_last_modified()) headers.add_header("Accept-Ranges", "bytes") start_response('200 OK', headers.items()) return _get_body(filename, method, block_size, charset)
def __init__(self, environ: dict): self.id = ''.join(random.choice(chars) for i in range(30)) self.body = dict() self.environ = environ self.wsgi_input: BufferedReader = cast(BufferedReader, environ.get('wsgi.input')) self.path = environ.get('PATH_INFO', '') self.method = environ.get('REQUEST_METHOD', '') self.port = environ.get('PORT', '') self.host = environ.get('HTTP_HOST', '') self.protocol = environ.get('HTTP_PROTOCOL', '') self.server_name = environ.get('SERVER_NAME', '') self.query = parse_qs(environ.get('QUERY_STRING', '')) self.params = dict() self.headers = Headers() self._parse_http_headers() self._read_request_body()
def get_partial_file(self, path, url, offset, end): headers = Headers([]) self.add_stat_headers(headers, path, url, end - offset + 1, offset, end) self.add_mime_headers(headers, path, url) self.add_cache_headers(headers, path, url) self.add_cors_headers(headers, path, url) self.add_extra_headers(headers, path, url) if self.add_headers_function: self.add_headers_function(headers, path, url) last_modified = parsedate(headers['Last-Modified']) gzip_file = self.get_alternative_encoding(headers, path, '.gz', 'gzip') brotli_file = self.get_alternative_encoding(headers, path, '.br', 'br') return StaticFile(plain_file=(path, headers), gzip_file=gzip_file, brotli_file=brotli_file, last_modified=last_modified)
def get_static_file(self, path, url, stat_cache=None): # Optimization: bail early if file does not exist if stat_cache is None and not os.path.exists(path): raise MissingFileError(path) headers = Headers([]) self.add_mime_headers(headers, path, url) self.add_cache_headers(headers, path, url) if self.allow_all_origins: headers["Access-Control-Allow-Origin"] = "*" if self.add_headers_function: self.add_headers_function(headers, path, url) return StaticFile( path, headers.items(), stat_cache=stat_cache, encodings={"gzip": path + ".gz", "br": path + ".br"}, )
def testExtras(self): h = Headers([]) self.assertEqual(str(h),'\r\n') h.add_header('foo','bar',baz="spam") self.assertEqual(h['foo'], 'bar; baz="spam"') self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n') h.add_header('Foo','bar',cheese=None) self.assertEqual(h.get_all('foo'), ['bar; baz="spam"', 'bar; cheese']) self.assertEqual(str(h), 'foo: bar; baz="spam"\r\n' 'Foo: bar; cheese\r\n' '\r\n' )
def newapp(environ, start_response): body = app(environ, capture) status = resp['status'] headers = Headers(resp['headers']) already = 'Content-Encoding' in headers accepted = 'gzip' in environ.get('HTTP_ACCEPT_ENCODING', '') if not accepted or already: # no compress start_response(status, list(headers.items())) return body content = gzip.compress(b''.join(body)) if hasattr(body, 'close'): body.close() headers['Content-Encoding'] = 'gzip' start_response(status, list(headers.items())) return [content]
def test_post_max_size(self) -> None: """test post max size""" with open("tests/wsgi/interfaces/wsgi.input", "rb") as input: with php.PHPWSGIInterface( { "REQUEST_METHOD": "POST", "wsgi.input": input, "CONTENT_TYPE": 'multipart/form-data;boundary="boundary"', "CONTENT_LENGTH": "324" }, start_response, 200, Headers(), None, post_max_size=100, stream_factory=NullStreamFactory()) as interface: self.assertEqual(len(interface.POST), 0) self.assertEqual(len(interface.FILES), 0)
def test_disabled_post(self) -> None: """test disabled POST""" with open("tests/wsgi/interfaces/wsgi.input", "rb") as input: with php.PHPWSGIInterface( { "REQUEST_METHOD": "POST", "wsgi.input": input, "CONTENT_TYPE": 'multipart/form-data;boundary="boundary"', "CONTENT_LENGTH": "324" }, start_response, 200, Headers(), None, stream_factory=None) as interface: self.assertEqual(len(interface.POST), 0) self.assertEqual(len(interface.FILES), 0) self.assertEqual(input.tell(), 0)
def finish_header(self): self.file = BytesIO() self.headers = Headers(self.headerlist) cdis = self.headers.get('Content-Disposition', '') ctype = self.headers.get('Content-Type', '') if not cdis: raise MultipartError('Content-Disposition header is missing.') self.disposition, self.options = parse_options_header(cdis) self.name = self.options.get('name') self.filename = self.options.get('filename') self.content_type, options = parse_options_header(ctype) self.charset = options.get('charset') or self.charset self.content_length = int(self.headers.get('Content-Length', '-1')) self.content_transfer_encoding = \ self.headers.get('Content-Transfer-Encoding') if self.content_transfer_encoding not in \ [None, 'base64', 'quoted-printable']: raise MultipartError('invalid Content-Transfer-Encoding')
def get_headers(self, headers_list, files): headers = Headers(headers_list) main_file = files[None] if len(files) > 1: headers["Vary"] = "Accept-Encoding" if "Last-Modified" not in headers: mtime = main_file.stat.st_mtime # Not all filesystems report mtimes, and sometimes they report an # mtime of 0 which we know is incorrect if mtime: headers["Last-Modified"] = formatdate(mtime, usegmt=True) if "ETag" not in headers: last_modified = parsedate(headers["Last-Modified"]) if last_modified: timestamp = int(mktime(last_modified)) headers["ETag"] = '"{:x}-{:x}"'.format(timestamp, main_file.stat.st_size) return headers
def __init__(self, response=None, status=200, charset='utf-8', content_type='text/html'): self.response = [] if response is None else response self.charset = charset self.headers = Headers() content_type = '{content_type}; charset={charset}'.format( content_type=content_type, charset=charset) self.headers.add_header('content-type', content_type) self._status = status
def __init__(self, content=b'', content_type='text/html', status_code=200): """init.""" if isinstance(content, bytes): self.content = content elif isinstance(content, str): self.content = content.encode('utf-8') self.content_type = '{}; charset=UTF-8'.format( content_type ) self.status_code = status_code self.reason_phrase = responses.get(status_code, 'Unknown Status Code') self.headers = [ ('Content-Type', self.content_type), ('Content-Length', str(len(self.content))) ] self.headers_dict = Headers(self.headers)
def package_response(self, outIO, environ, start_response, headers = []): newheaders = headers headers = [('Content-type', 'application/octet-stream')] # my understanding of spec. If unknown = binary headersIface = Headers(headers) for header in newheaders: headersIface[header[0]] = '; '.join(header[1:]) if hasattr(outIO,'fileno') and 'wsgi.file_wrapper' in environ: outIO.seek(0) retobj = environ['wsgi.file_wrapper']( outIO, self.bufsize ) elif hasattr(outIO,'read'): outIO.seek(0) retobj = iter( lambda: outIO.read(self.bufsize), '' ) else: retobj = outIO start_response("200 OK", headers) return retobj
def __call__(self, environ, start_response): selector_matches = (environ.get('wsgiorg.routing_args') or ([], {}))[1] if 'working_path' in selector_matches: # working_path is a custom key that I just happened to decide to use # for marking the portion of the URI that is palatable for static serving. # 'working_path' is the name of a regex group fed to WSGIHandlerSelector path_info = selector_matches['working_path'].decode('utf8') else: path_info = environ.get('PATH_INFO', '').decode('utf8') # this, i hope, safely turns the relative path into OS-specific, absolute. full_path = os.path.abspath( os.path.join(self.content_path, path_info.strip('/'))) _pp = os.path.abspath(self.content_path) if not full_path.startswith(_pp): return self.canned_handlers(environ, start_response, 'forbidden') if not os.path.isfile(full_path): return self.canned_handlers(environ, start_response, 'not_found') mtime = os.stat(full_path).st_mtime etag, last_modified = str(mtime), email.utils.formatdate(mtime) headers = [('Content-type', 'text/plain'), ('Date', email.utils.formatdate(time.time())), ('Last-Modified', last_modified), ('ETag', etag)] headersIface = Headers(headers) headersIface['Content-Type'] = mimetypes.guess_type( full_path)[0] or 'application/octet-stream' if_modified = environ.get('HTTP_IF_MODIFIED_SINCE') if if_modified and (email.utils.parsedate(if_modified) >= email.utils.parsedate(last_modified)): return self.canned_handlers(environ, start_response, 'not_modified', headers) if_none = environ.get('HTTP_IF_NONE_MATCH') if if_none and (if_none == '*' or etag in if_none): return self.canned_handlers(environ, start_response, 'not_modified', headers) file_like = open(full_path, 'rb') return self.package_response(file_like, environ, start_response, headers)
def package_response(self, outIO, environ, start_response, headers=[]): newheaders = headers headers = [('Content-type', 'application/octet-stream') ] # my understanding of spec. If unknown = binary headersIface = Headers(headers) for header in newheaders: headersIface[header[0]] = '; '.join(header[1:]) retobj = outIO if hasattr(outIO, 'fileno') and 'wsgi.file_wrapper' in environ: outIO.seek(0) retobj = environ['wsgi.file_wrapper'](outIO, self.bufsize) # TODO: I think this does not work well on NWSGI 2.0. Talk to Jeff about this for 3.0 elif hasattr(outIO, 'read'): outIO.seek(0) retobj = iter(lambda: outIO.read(self.bufsize), '') start_response("200 OK", headers) return retobj
def test_cache(self) -> None: """test opcache functions""" with MemoryCache(Directory("./tests/embedding", compiler), UnboundedCacheStrategy()) as cache: with php.PHPWSGIInterface({}, start_response, 200, Headers(), cache) as interface: self.assertFalse( interface.opcache_is_script_cached("syntax.pyhp")) self.assertTrue(interface.opcache_compile_file("syntax.pyhp")) self.assertTrue(interface.opcache_invalidate("syntax.pyhp")) self.assertTrue( interface.opcache_is_script_cached("syntax.pyhp")) self.assertTrue( interface.opcache_invalidate("syntax.pyhp", force=True)) self.assertFalse( interface.opcache_is_script_cached("syntax.pyhp")) self.assertTrue(interface.opcache_compile_file("syntax.pyhp")) self.assertTrue(interface.opcache_reset()) self.assertFalse( interface.opcache_is_script_cached("syntax.pyhp"))
def newapp(environ, start_response): raw = app(environ, capture) status = resp['status'] headers = Headers(resp['headers']) if (not 'Last-Modified' in headers or not environ.get('HTTP_IF_MODIFIED_SINCE')): start_response(status, list(headers.items())) return raw last_m = eutils.parsedate(headers['Last-Modified']) since_m = eutils.parsedate(environ['HTTP_IF_MODIFIED_SINCE']) if since_m < last_m: start_response(status, list(headers.items())) return raw else: start_response('304 Not Modified', list(headers.items())) if hasattr(raw, 'close'): raw.close() return [b'']
def __init__(self, id, url, cookie='', headers=HEADERS_CHROME, host=None, port=None, path=None, protocol=None, proxy=None, max_thread=-1, range_format='Range: bytes=%d-%d'): self.id = id self.url = url self.host = host if host is not None else getattr(self, 'host', None) self.port = port if port is not None else getattr(self, 'port', None) self.path = path if path is not None else getattr(self, 'path', None) self.protocol = protocol if protocol is not None else getattr( self, 'protocol', None) self.cookie = cookie if isinstance(headers, Headers): self.headers = headers elif isinstance(headers, dict): self.headers = Headers(list(headers.items())) else: raise ValueError('headers must be an instance of dict or Headers') self.etag = None self.proxy = proxy self.target = Target() self.max_thread = max_thread self.range_format = range_format
def http_request(self, method: str, path: str, headers: Dict = None, body: Any = None, fields: Dict[str, Any] = None, files: Dict[str, TextIO] = None) -> HttpResponse: complete_url = self.base_url + path url_elements = urlparse(complete_url) connection = HTTPConnection(host=url_elements.hostname, port=url_elements.port) combined_headers = self.default_headers.copy() byte_body: bytes = ''.encode(UTF_8) if files or fields: byte_body, boundary = self.encode_multipart_formdata( files=files if files else dict(), fields=fields if fields else dict()) combined_headers[ CONTENT_TYPE] = 'multipart/form-data; boundary=%s' % boundary elif body: byte_body = self.convert_body_to_bytes(body=body) combined_headers.update({CONTENT_LENGTH: str(len(byte_body))}) if headers is not None: combined_headers.update(headers) connection.request(method=method, url=url_elements.path, body=byte_body, headers=combined_headers) native_response: HTTPResponse = connection.getresponse() byte_response: bytes = native_response.read() if byte_response and native_response.getheader( CONTENT_TYPE, 'plain/text') == 'application/json': response_body = json.loads(byte_response) else: response_body = byte_response response = HttpResponse(status_code=native_response.status, headers=Headers(native_response.getheaders()), message=native_response.reason, url=complete_url, data=response_body) return response
def testBytes(self): h = Headers([ (b"Content-Type", b"text/plain; charset=utf-8"), ]) self.assertEqual("text/plain; charset=utf-8", h.get("Content-Type")) h[b"Foo"] = bytes(b"bar") self.assertEqual("bar", h.get("Foo")) self.assertEqual("bar", h.get(b"Foo")) h.setdefault(b"Bar", b"foo") self.assertEqual("foo", h.get("Bar")) self.assertEqual("foo", h.get(b"Bar")) h.add_header(b'content-disposition', b'attachment', filename=b'bud.gif') self.assertEqual('attachment; filename="bud.gif"', h.get("content-disposition")) del h['content-disposition'] self.assertTrue(b'content-disposition' not in h)
def _construct_part(self, part, line, newline) -> None: """ Add headers to the Part as they are parsed. """ line = line.decode(self.charset) if not newline: self.state = States.BUILDING_HEADERS_NEED_DATA return if not line.strip(): # blank line -> end of header segment part.headers = Headers(part.headerlist) content_disposition = part.headers.get("Content-Disposition", "") content_type = part.headers.get("Content-Type", "") if not content_disposition: raise MalformedData("Content-Disposition header is missing.") part.disposition, part.options = parse_options_header( content_disposition) part.name = part.options.get("name") part.filename = part.options.get("filename") part.content_type, options = parse_options_header(content_type) part.charset = options.get("charset") or part.charset content_length = part.headers.get("Content-Length") if content_length is not None: self.expected_part_size = int(content_length) self.current_part = None self.state = States.BUILDING_BODY return if ":" not in line: raise MalformedData("Syntax error in header: No colon.") name, value = line.split(":", 1) part.headerlist.append((name.strip(), value.strip()))
def test_server(self) -> None: """test PHPWSGIInterface.SERVER""" environ = { "PATH_INFO": "/test", "SCRIPT_NAME": "/test.pyhp", "HTTP_AUTHORIZATION": "Basic YWxhZGRpbjpvcGVuc2VzYW1l", "QUERY_STRING": "a=b", "CUSTOM": "test" } timestamp = time.time() with php.PHPWSGIInterface(environ, start_response, 200, Headers(), None) as interface: self.assertEqual(interface.SERVER["argv"], "a=b") self.assertEqual(interface.SERVER["PHP_SELF"], "/test.pyhp/test") self.assertEqual(interface.SERVER["PHP_AUTH_USER"], "aladdin") self.assertEqual(interface.SERVER["AUTH_TYPE"], "basic") self.assertEqual(interface.SERVER["PHP_AUTH_PW"], "opensesame") self.assertNotIn("PHP_AUTH_DIGEST", interface.SERVER) self.assertEqual(int(interface.SERVER["REQUEST_TIME_FLOAT"]), interface.SERVER["REQUEST_TIME"]) self.assertTrue(timestamp <= interface.SERVER["REQUEST_TIME_FLOAT"] <= time.time()) self.assertIn("CUSTOM", interface.SERVER)
def do_GET(self, environ, start_response): headers = Headers() video_url = "{PATH_INFO}?{QUERY_STRING}".format(**environ).strip("/") if video_url == "?": raise self.HTTPError(400, more="no URL provided") try: videos = self.downloader.get_videos(video_url) except CannotDownload as cad: raise self.HTTPError(400, "Cannot download", more=str(cad)) if len(videos) != 1: raise self.HTTPError(400, more="playlists not supported yet") video = videos[0] audio_file = self.downloader.cache_dir / video.path assert audio_file.exists() filesize = audio_file.stat().st_size headers.add_header("Content-Disposition", "attachment", filename=video.title) headers.add_header("Content-Type", "audio/mpeg") headers.add_header("Content-Length", str(filesize)) start_response("200 OK", headers.items()) return FileWrapper(audio_file.open("rb"))
def request(self, method, path, environ=None, **kw): """Send a request to the application under test. The environment will be populated with some default keys. Additional keyword arguments will be interpreted as request headers. For example, passing x_foo=1 will add a request header "X-Foo: 1". """ if environ is None: environ = {} # setup_testing_defaults() uses '127.0.0.1', but localhost is easier # to type when testing. environ.setdefault('SERVER_NAME', 'localhost') environ.setdefault('QUERY_STRING', '') # silence validator warning setup_testing_defaults(environ) environ['REQUEST_METHOD'] = method environ['PATH_INFO'] = path for k, v in kw.items(): key = k.upper() if key not in ('CONTENT_TYPE', 'CONTENT_LENGTH'): key = 'HTTP_' + key environ[key] = str(v) start_response_rv = [] def start_response(status, headers, exc_info=None): # TODO handle exc_info != None start_response_rv.extend([status, headers]) wsgi_app = validator(self.app) app_iter = wsgi_app(environ, start_response) try: body = ''.join(app_iter) finally: if hasattr(app_iter, 'close'): app_iter.close() statusline, headerlist = start_response_rv return wsgi_response(statusline, Headers(headerlist), body)
def test_header(self) -> None: """test PHPWSGIInterface.header""" with php.PHPWSGIInterface({}, start_response, 200, Headers(), None) as interface: interface.header("test: true") self.assertEqual(interface.headers.get_all("test"), ["true"]) interface.header("test: maybe") self.assertEqual(interface.headers.get_all("test"), ["maybe"]) interface.header("test: true", replace=False) self.assertEqual(interface.headers.get_all("test"), ["maybe", "true"]) interface.header("test: false", response_code=400) self.assertEqual(interface.headers.get_all("test"), ["false"]) self.assertEqual(interface.status_code, 400) interface.header("Location: test") self.assertEqual(interface.status_code, 302) interface.header("Location: test", response_code=308) self.assertEqual(interface.status_code, 308) interface.header("Location: test") self.assertEqual(interface.status_code, 308) with self.assertRaises(ValueError): interface.header("Attack: Injection\r\nPayload: test") with self.assertRaises(ValueError): interface.header("Attack: Injection\nPayload: test")
def test_eq(self) -> None: """test SimpleWSGIInterface.__eq__""" start_response = lambda a, b, c: lambda x: None interfaces = (SimpleWSGIInterface({}, start_response, "200 OK", Headers(), None), SimpleWSGIInterface({"a": 42}, start_response, "200 OK", Headers(), None), SimpleWSGIInterface({}, lambda a, b, c: None, "200 OK", Headers(), None), SimpleWSGIInterface({}, start_response, "400 Bad Request", Headers(), None), SimpleWSGIInterface({}, start_response, "200 OK", Headers([("Context-Type", "test/plain")]), None), SimpleWSGIInterface({}, start_response, "200 OK", Headers(), 42)) for interface in interfaces: self.assertEqual([obj for obj in interfaces if obj == interface], [interface]) self.assertNotEqual(interfaces[0], 42)
def getPreview(layer): """ Get a type string and dynamic map viewer HTML for a given layer. """ return 200, Headers([('Content-Type', 'text/html')]), Core._preview(layer)
def requestHandler2(config_hint, path_info, query_string=None, script_name=''): """ Generate a set of headers and response body for a given request. TODO: Replace requestHandler() with this function in TileStache 2.0.0. Requires a configuration and PATH_INFO (e.g. "/example/0/0/0.png"). Config_hint parameter can be a path string for a JSON configuration file or a configuration object with 'cache', 'layers', and 'dirpath' properties. Query string is optional, currently used for JSON callbacks. Calls Layer.getTileResponse() to render actual tiles, and getPreview() to render preview.html. """ headers = Headers([]) try: # ensure that path_info is at least a single "/" path_info = '/' + (path_info or '').lstrip('/') layer = requestLayer(config_hint, path_info) query = parse_qs(query_string or '') try: callback = query['callback'][0] except KeyError: callback = None # # Special case for index page. # if path_info == '/': mimetype, content = getattr(layer.config, 'index', ('text/plain', 'TileStache says hello.')) return 200, Headers([('Content-Type', mimetype)]), content coord, extension = splitPathInfo(path_info)[1:] if extension == 'html' and coord is None: status_code, headers, content = getPreview(layer) elif extension.lower() in layer.redirects: other_extension = layer.redirects[extension.lower()] redirect_uri = script_name redirect_uri += mergePathInfo(layer.name(), coord, other_extension) if query_string: redirect_uri += '?' + query_string headers['Location'] = redirect_uri headers['Content-Type'] = 'text/plain' return 302, headers, 'You are being redirected to %s\n' % redirect_uri else: status_code, headers, content = layer.getTileResponse(coord, extension) if layer.allowed_origin: headers.setdefault('Access-Control-Allow-Origin', layer.allowed_origin) if callback and 'json' in headers['Content-Type']: headers['Content-Type'] = 'application/javascript; charset=utf-8' content = '%s(%s)' % (callback, content) if layer.max_cache_age is not None: expires = datetime.utcnow() + timedelta(seconds=layer.max_cache_age) headers.setdefault('Expires', expires.strftime('%a %d %b %Y %H:%M:%S GMT')) headers.setdefault('Cache-Control', 'public, max-age=%d' % layer.max_cache_age) except Core.KnownUnknown, e: out = StringIO() print >> out, 'Known unknown!' print >> out, e print >> out, '' print >> out, '\n'.join(Core._rummy()) headers['Content-Type'] = 'text/plain' status_code, content = 500, out.getvalue()