wrap another stream to disallow reading it past a number of bytes based on the implementation from werkzeug wsgi limitedstream see https github compalletswerkzeugblobdbf78f67srcwerkzeugwsgi pyl828 if pathinfo is empty e g accessing the scriptname url without a trailing slash operate as if was requested be careful to only replace the first slash in the path because of http testsomething and http testsomething being different as stated in rfc 3986 set contenttype contentparams and encoding the wsgi spec says querystring may be absent if wsgi filewrapper is used the wsgi server does not call close on the response but on the file wrapper patch it to use response close instead which takes care of closing all files return the http request s pathinfo as a string pathinfo getbytesfromwsgienviron pathinfo return repercentbrokenunicodepathinfo decode def getscriptnameenviron if settings forcescriptname is not none return settings forcescriptname if apache s modrewrite had a whack at the url apache set either scripturl or redirecturl to the full resource url before applying any rewrites unfortunately not every web server lighttpd passes this information through all the time so forcescriptname above is still needed scripturl getbytesfromwsgienviron scripturl or getbytesfromwsgi environ redirecturl if scripturl if b in scripturl modwsgi squashes multiple successive slashes in pathinfo do the same with scripturl before manipulating paths 17133 scripturl slashesre subb scripturl pathinfo getbytesfromwsgienviron pathinfo scriptname scripturl removesuffixpathinfo else scriptname getbytesfromwsgienviron scriptname return scriptname decode def getbytesfromwsgienviron key default value environ getkey default nonascii values in the wsgi environ are arbitrarily decoded with iso88591 this is wrong for django websites where utf8 is the default reencode to recover the original bytestring return value encodeiso88591 def getstrfromwsgienviron key default value getbytesfromwsgienviron key default return value decodeerrorsreplace wrap another stream to disallow reading it past a number of bytes based on the implementation from werkzeug wsgi limitedstream see https github com pallets werkzeug blob dbf78f67 src werkzeug wsgi py l828 if path_info is empty e g accessing the script_name url without a trailing slash operate as if was requested be careful to only replace the first slash in the path because of http test something and http test something being different as stated in rfc 3986 set content_type content_params and encoding the wsgi spec says query_string may be absent if wsgi file_wrapper is used the wsgi server does not call close on the response but on the file wrapper patch it to use response close instead which takes care of closing all files return the http request s path_info as a string return the equivalent of the http request s script_name environment variable if apache mod_rewrite is used return what would have been the script name prior to any rewriting so it s the script name as seen from the client s perspective unless the force_script_name setting is set to anything if apache s mod_rewrite had a whack at the url apache set either script_url or redirect_url to the full resource url before applying any rewrites unfortunately not every web server lighttpd passes this information through all the time so force_script_name above is still needed mod_wsgi squashes multiple successive slashes in path_info do the same with script_url before manipulating paths 17133 get a value from the wsgi environ dictionary as bytes key and default should be strings non ascii values in the wsgi environ are arbitrarily decoded with iso 8859 1 this is wrong for django websites where utf 8 is the default re encode to recover the original bytestring get a value from the wsgi environ dictionary as str key and default should be str objects
from io import IOBase

from django.conf import settings
from django.core import signals
from django.core.handlers import base
from django.http import HttpRequest, QueryDict, parse_cookie
from django.urls import set_script_prefix
from django.utils.encoding import repercent_broken_unicode
from django.utils.functional import cached_property
from django.utils.regex_helper import _lazy_re_compile

_slashes_re = _lazy_re_compile(rb"/+")


class LimitedStream(IOBase):
    

    def __init__(self, stream, limit):
        self._read = stream.read
        self._readline = stream.readline
        self._pos = 0
        self.limit = limit

    def read(self, size=-1, /):
        _pos = self._pos
        limit = self.limit
        if _pos >= limit:
            return b""
        if size == -1 or size is None:
            size = limit - _pos
        else:
            size = min(size, limit - _pos)
        data = self._read(size)
        self._pos += len(data)
        return data

    def readline(self, size=-1, /):
        _pos = self._pos
        limit = self.limit
        if _pos >= limit:
            return b""
        if size == -1 or size is None:
            size = limit - _pos
        else:
            size = min(size, limit - _pos)
        line = self._readline(size)
        self._pos += len(line)
        return line


class WSGIRequest(HttpRequest):
    def __init__(self, environ):
        script_name = get_script_name(environ)
        
        
        path_info = get_path_info(environ) or "/"
        self.environ = environ
        self.path_info = path_info
        
        
        
        self.path = "%s/%s" % (script_name.rstrip("/"), path_info.replace("/", "", 1))
        self.META = environ
        self.META["PATH_INFO"] = path_info
        self.META["SCRIPT_NAME"] = script_name
        self.method = environ["REQUEST_METHOD"].upper()
        
        self._set_content_type_params(environ)
        try:
            content_length = int(environ.get("CONTENT_LENGTH"))
        except (ValueError, TypeError):
            content_length = 0
        self._stream = LimitedStream(self.environ["wsgi.input"], content_length)
        self._read_started = False
        self.resolver_match = None

    def _get_scheme(self):
        return self.environ.get("wsgi.url_scheme")

    @cached_property
    def GET(self):
        
        raw_query_string = get_bytes_from_wsgi(self.environ, "QUERY_STRING", "")
        return QueryDict(raw_query_string, encoding=self._encoding)

    def _get_post(self):
        if not hasattr(self, "_post"):
            self._load_post_and_files()
        return self._post

    def _set_post(self, post):
        self._post = post

    @cached_property
    def COOKIES(self):
        raw_cookie = get_str_from_wsgi(self.environ, "HTTP_COOKIE", "")
        return parse_cookie(raw_cookie)

    @property
    def FILES(self):
        if not hasattr(self, "_files"):
            self._load_post_and_files()
        return self._files

    POST = property(_get_post, _set_post)


class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = "%d %s" % (response.status_code, response.reason_phrase)
        response_headers = [
            *response.items(),
            *(("Set-Cookie", c.output(header="")) for c in response.cookies.values()),
        ]
        start_response(status, response_headers)
        if getattr(response, "file_to_stream", None) is not None and environ.get(
            "wsgi.file_wrapper"
        ):
            
            
            
            response.file_to_stream.close = response.close
            response = environ["wsgi.file_wrapper"](
                response.file_to_stream, response.block_size
            )
        return response


def get_path_info(environ):
    
    path_info = get_bytes_from_wsgi(environ, "PATH_INFO", "/")

    return repercent_broken_unicode(path_info).decode()


def get_script_name(environ):
    
    if settings.FORCE_SCRIPT_NAME is not None:
        return settings.FORCE_SCRIPT_NAME

    
    
    
    
    
    script_url = get_bytes_from_wsgi(environ, "SCRIPT_URL", "") or get_bytes_from_wsgi(
        environ, "REDIRECT_URL", ""
    )

    if script_url:
        if b"//" in script_url:
            
            
            script_url = _slashes_re.sub(b"/", script_url)
        path_info = get_bytes_from_wsgi(environ, "PATH_INFO", "")
        script_name = script_url.removesuffix(path_info)
    else:
        script_name = get_bytes_from_wsgi(environ, "SCRIPT_NAME", "")

    return script_name.decode()


def get_bytes_from_wsgi(environ, key, default):
    
    value = environ.get(key, default)
    
    
    
    return value.encode("iso-8859-1")


def get_str_from_wsgi(environ, key, default):
    
    value = get_bytes_from_wsgi(environ, key, default)
    return value.decode(errors="replace")