Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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()
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
 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()
Exemplo n.º 5
0
 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)
Exemplo n.º 6
0
 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"},
     )
Exemplo n.º 7
0
    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'
        )
Exemplo n.º 8
0
    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]
Exemplo n.º 9
0
 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)
Exemplo n.º 10
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)
Exemplo n.º 11
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')
Exemplo n.º 12
0
 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
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
    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
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
    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
Exemplo n.º 18
0
 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"))
Exemplo n.º 19
0
    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'']
Exemplo n.º 20
0
    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
Exemplo n.º 21
0
 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
Exemplo n.º 22
0
    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)
Exemplo n.º 23
0
    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()))
Exemplo n.º 24
0
 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)
Exemplo n.º 25
0
    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"))
Exemplo n.º 26
0
    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)
Exemplo n.º 27
0
 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")
Exemplo n.º 28
0
 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)
Exemplo n.º 29
0
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)
Exemplo n.º 30
0
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()