base classes for servergateway implementations from util import filewrapper guessscheme ishopbyhop from headers import headers import sys os time all basehandler simplehandler basecgihandler cgihandler iiscgihandler readenviron weekday and month names for http datetime formatting always english weekdayname mon tue wed thu fri sat sun monthname none dummy so we can use 1based month numbers jan feb mar apr may jun jul aug sep oct nov dec def formatdatetimetimestamp year month day hh mm ss wd y z time gmtimetimestamp return s 02d 3s 4d 02d 02d 02d gmt weekdaynamewd day monthnamemonth year hh mm ss isrequest scriptname pathinfo querystring requestmethod authtype contenttype contentlength https remoteuser remoteident contains def needstranscodek return isrequestk or k startswith http or k startswith ssl or k startswith redirect and needstranscodek9 def readenviron take the basic environment from nativeunicode os environ attempt to fix up the variables that come from the http request to compensate for the bytesunicode decoding step that will already have taken place on win32 the os environ is natively unicode different servers decode the request bytes using different encodings on iis the http request will be decoded as utf8 as long as the input is a valid utf8 sequence otherwise it is decoded using the system code page mbcs with no way to detect this has happened because utf8 is the more likely encoding and mbcs is inherently unreliable an mbcs string that happens to be valid utf8 will not be decoded as mbcs always recreate the original bytes as utf8 apache modcgi writes bytesasunicode as if iso88591 direct to the unicode environ no modification needed python 3 s http server cgihttprequesthandler decodes using the urllib unquote default of utf8 amongst other issues for other servers guess that they have written bytes to the environ using stdio byteoriented interfaces ending up with the system code page recover bytes from unicode environ using surrogate escapes where available python 3 1 manage the invocation of a wsgi application configuration parameters can override persubclass or perinstance wsgiversion 1 0 wsgimultithread true wsgimultiprocess true wsgirunonce false originserver true we are transmitting direct to client httpversion 1 0 version that should be used for response serversoftware none string name of server software if any osenviron is used to supply configuration from the os environment by default it s a copy of os environ as of import time but you can override this in e g your init method osenviron readenviron collaborator classes wsgifilewrapper filewrapper set to none to disable headersclass headers must be a headerslike class error handling also persubclass or perinstance tracebacklimit none print entire traceback to self getstderr errorstatus 500 internal server error errorheaders contenttype textplain errorbody ba server error occurred please contact the administrator state variables don t mess with these status result none headerssent false headers none bytessent 0 def runself application note to self don t move the close asynchronous servers shouldn t call close from finishresponse so if you close anywhere but the doubleerror branch here you ll break asynchronous servers by prematurely closing async servers must return from run without closing if there might still be output to iterate over we expect the client to close the connection abruptly from time to time if we get an error handling an error just give up already set up the environment for one request env self environ self osenviron copy self addcgivars env wsgi input self getstdin env wsgi errors self getstderr env wsgi version self wsgiversion env wsgi runonce self wsgirunonce env wsgi urlscheme self getscheme env wsgi multithread self wsgimultithread env wsgi multiprocess self wsgimultiprocess if self wsgifilewrapper is not none env wsgi filewrapper self wsgifilewrapper if self originserver and self serversoftware env setdefault serversoftware self serversoftware def finishresponseself try if not self resultisfile or not self sendfile for data in self result self writedata self finishcontent except call close on the iterable returned by the wsgi application in case of an exception if hasattrself result close self result close raise else we only call close when no exception is raised because it will set status result headers and environ fields to none see bpo29183 for more details self close def getschemeself compute contentlength or switch to chunked encoding if possible try blocks lenself result except typeerror attributeerror notimplementederror pass else if blocks1 self headers contentlength strself bytessent return xxx try for chunked encoding if origin server and client is 1 1 def cleanupheadersself if contentlength not in self headers self setcontentlength def startresponseself status headers excinfonone convertcheck value type if typevalue is str return value raise assertionerror 0 must be of type str got 1 formattitle reprvalue def sendpreambleself write callable as specified by pep 3333 assert typedata is bytes write argument must be a bytes instance if not self status raise assertionerrorwrite before startresponse elif not self headerssent before the first output send the stored headers self bytessent lendata make sure we know contentlength self sendheaders else self bytessent lendata xxx check contentlength and truncate if too many bytes written self writedata self flush def sendfileself return false no platformspecific transmission by default def finishcontentself only zero contentlength if not set by the application so that head requests can be satisfied properly see 3839 close the iterable if needed and reset all instance vars subclasses may want to also drop the client connection transmit headers to the client via self write self cleanupheaders self headerssent true if not self originserver or self clientismodern self sendpreamble self writebytesself headers def resultisfileself true if client can accept status and headers return self environ serverprotocol upper http0 9 def logexceptionself excinfo try from traceback import printexception stderr self getstderr printexception excinfo0 excinfo1 excinfo2 self tracebacklimit stderr stderr flush finally excinfo none def handleerrorself xxx else attempt advanced recovery techniques for html or text wsgi miniapp to create error output by default this just uses the errorstatus errorheaders and errorbody attributes to generate an output page it can be overridden in a subclass to dynamically generate diagnostics choose an appropriate message for the user s preferred language etc note however that it s not recommended from a security perspective to spit out diagnostics to any old user ideally you should have to do something special to enable diagnostic output which is why we don t include any here pure abstract methods must be overridden in subclasses override in subclass to buffer data for send to client it s okay if this method actually transmits the data basehandler just separates write and flush operations for greater efficiency when the underlying system actually has such a distinction override in subclass to force sending of recent write calls it s okay if this method is a noop i e if write actually sends the data override in subclass to return suitable wsgi input raise notimplementederror def getstderrself override in subclass to insert cgi variables in self environ raise notimplementederror class simplehandlerbasehandler cgilike systems using inputoutputerror streams and environ mapping usage handler basecgihandlerinp out err env handler runapp this handler class is useful for gateway protocols like readyexec and fastcgi that have usable inputoutputerror streams and an environment mapping it s also the base class for cgihandler which just uses sys stdin os environ and so on the constructor also takes keyword arguments multithread and multiprocess defaulting to true and false respectively to control the configuration sent to the application it sets originserver to false to enable cgilike output and assumes that wsgi runonce is false cgibased invocation via sys stdinstdoutstderr and os environ usage cgihandler runapp the difference between this class and basecgihandler is that it always uses wsgi runonce of true wsgi multithread of false and wsgi multiprocess of true it does not take any initialization parameters but always uses sys stdin os environ and friends if you need to override any of these parameters use basecgihandler instead do not allow os environ to leak between requests in google app engine and other multirun cgi use cases this is not easily testable see http bugs python orgissue7250 cgibased invocation with workaround for iis path bug this handler should be used in preference to cgihandler when deploying on microsoft iis without having set the config allowpathinfo option iis7 or metabase allowpathinfoforscriptmappings iis7 by default iis gives a pathinfo that duplicates the scriptname at the front causing problems for wsgi applications that wish to implement routing this handler strips any such duplicated path iis can be configured to pass the correct pathinfo but this causes another bug where pathtranslated is wrong luckily this variable is rarely used and is not guaranteed by wsgi on iis7 though the setting can only be made on a vhost level affecting all other script mappings many of which break when exposed to the pathtranslated bug for this reason iis7 is almost never deployed with the fix even iis7 rarely uses it because there is still no ui for it there is no way for cgi code to tell whether the option was set so a separate handler class is provided weekday and month names for http date time formatting always english dummy so we can use 1 based month numbers read environment fixing http variables take the basic environment from native unicode os environ attempt to fix up the variables that come from the http request to compensate for the bytes unicode decoding step that will already have taken place on win32 the os environ is natively unicode different servers decode the request bytes using different encodings on iis the http request will be decoded as utf 8 as long as the input is a valid utf 8 sequence otherwise it is decoded using the system code page mbcs with no way to detect this has happened because utf 8 is the more likely encoding and mbcs is inherently unreliable an mbcs string that happens to be valid utf 8 will not be decoded as mbcs always recreate the original bytes as utf 8 apache mod_cgi writes bytes as unicode as if iso 8859 1 direct to the unicode environ no modification needed python 3 s http server cgihttprequesthandler decodes using the urllib unquote default of utf 8 amongst other issues for other servers guess that they have written bytes to the environ using stdio byte oriented interfaces ending up with the system code page recover bytes from unicode environ using surrogate escapes where available python 3 1 manage the invocation of a wsgi application configuration parameters can override per subclass or per instance we are transmitting direct to client version that should be used for response string name of server software if any os_environ is used to supply configuration from the os environment by default it s a copy of os environ as of import time but you can override this in e g your __init__ method collaborator classes set to none to disable must be a headers like class error handling also per subclass or per instance print entire traceback to self get_stderr state variables don t mess with these invoke the application note to self don t move the close asynchronous servers shouldn t call close from finish_response so if you close anywhere but the double error branch here you ll break asynchronous servers by prematurely closing async servers must return from run without closing if there might still be output to iterate over we expect the client to close the connection abruptly from time to time if we get an error handling an error just give up already and let the actual server figure it out set up the environment for one request send any iterable data then close self and the iterable subclasses intended for use in asynchronous servers will want to redefine this method such that it sets up callbacks in the event loop to iterate over the data and to call self close once the response is finished call close on the iterable returned by the wsgi application in case of an exception we only call close when no exception is raised because it will set status result headers and environ fields to none see bpo 29183 for more details return the url scheme being used compute content length or switch to chunked encoding if possible xxx try for chunked encoding if origin server and client is 1 1 make any necessary header changes or defaults subclasses can extend this to add other defaults start_response callable as specified by pep 3333 avoid dangling circular ref convert check value type transmit version status date server via self _write write callable as specified by pep 3333 before the first output send the stored headers make sure we know content length xxx check content length and truncate if too many bytes written platform specific file transmission override this method in subclasses to support platform specific file transmission it is only called if the application s return iterable self result is an instance of self wsgi_file_wrapper this method should return a true value if it was able to actually transmit the wrapped file like object using a platform specific approach it should return a false value if normal iteration should be used instead an exception can be raised to indicate that transmission was attempted but failed note this method should call self send_headers if self headers_sent is false and it is going to attempt direct transmission of the file no platform specific transmission by default ensure headers and content have both been sent only zero content length if not set by the application so that head requests can be satisfied properly see 3839 xxx check if content length was too short close the iterable if needed and reset all instance vars subclasses may want to also drop the client connection transmit headers to the client via self _write true if self result is an instance of self wsgi_file_wrapper true if client can accept status and headers log the exc_info tuple in the server log subclasses may override to retarget the output or change its format log current error and send error output to client if possible xxx else attempt advanced recovery techniques for html or text wsgi mini app to create error output by default this just uses the error_status error_headers and error_body attributes to generate an output page it can be overridden in a subclass to dynamically generate diagnostics choose an appropriate message for the user s preferred language etc note however that it s not recommended from a security perspective to spit out diagnostics to any old user ideally you should have to do something special to enable diagnostic output which is why we don t include any here pure abstract methods must be overridden in subclasses override in subclass to buffer data for send to client it s okay if this method actually transmits the data basehandler just separates write and flush operations for greater efficiency when the underlying system actually has such a distinction override in subclass to force sending of recent _write calls it s okay if this method is a no op i e if _write actually sends the data override in subclass to return suitable wsgi input override in subclass to return suitable wsgi errors override in subclass to insert cgi variables in self environ handler that s just initialized with streams environment etc this handler subclass is intended for synchronous http 1 0 origin servers and handles sending the entire response output given the correct inputs usage handler simplehandler inp out err env multithread false multiprocess true handler run app cgi like systems using input output error streams and environ mapping usage handler basecgihandler inp out err env handler run app this handler class is useful for gateway protocols like readyexec and fastcgi that have usable input output error streams and an environment mapping it s also the base class for cgihandler which just uses sys stdin os environ and so on the constructor also takes keyword arguments multithread and multiprocess defaulting to true and false respectively to control the configuration sent to the application it sets origin_server to false to enable cgi like output and assumes that wsgi run_once is false cgi based invocation via sys stdin stdout stderr and os environ usage cgihandler run app the difference between this class and basecgihandler is that it always uses wsgi run_once of true wsgi multithread of false and wsgi multiprocess of true it does not take any initialization parameters but always uses sys stdin os environ and friends if you need to override any of these parameters use basecgihandler instead do not allow os environ to leak between requests in google app engine and other multi run cgi use cases this is not easily testable see http bugs python org issue7250 cgi based invocation with workaround for iis path bug this handler should be used in preference to cgihandler when deploying on microsoft iis without having set the config allowpathinfo option iis 7 or metabase allowpathinfoforscriptmappings iis 7 by default iis gives a path_info that duplicates the script_name at the front causing problems for wsgi applications that wish to implement routing this handler strips any such duplicated path iis can be configured to pass the correct path_info but this causes another bug where path_translated is wrong luckily this variable is rarely used and is not guaranteed by wsgi on iis 7 though the setting can only be made on a vhost level affecting all other script mappings many of which break when exposed to the path_translated bug for this reason iis 7 is almost never deployed with the fix even iis7 rarely uses it because there is still no ui for it there is no way for cgi code to tell whether the option was set so a separate handler class is provided
from .util import FileWrapper, guess_scheme, is_hop_by_hop
from .headers import Headers

import sys, os, time

__all__ = [
    'BaseHandler', 'SimpleHandler', 'BaseCGIHandler', 'CGIHandler',
    'IISCGIHandler', 'read_environ'
]


_weekdayname = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
_monthname = [None, 
              "Jan", "Feb", "Mar", "Apr", "May", "Jun",
              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

def format_date_time(timestamp):
    year, month, day, hh, mm, ss, wd, y, z = time.gmtime(timestamp)
    return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
        _weekdayname[wd], day, _monthname[month], year, hh, mm, ss
    )

_is_request = {
    'SCRIPT_NAME', 'PATH_INFO', 'QUERY_STRING', 'REQUEST_METHOD', 'AUTH_TYPE',
    'CONTENT_TYPE', 'CONTENT_LENGTH', 'HTTPS', 'REMOTE_USER', 'REMOTE_IDENT',
}.__contains__

def _needs_transcode(k):
    return _is_request(k) or k.startswith('HTTP_') or k.startswith('SSL_') \
        or (k.startswith('REDIRECT_') and _needs_transcode(k[9:]))

def read_environ():
    
    enc = sys.getfilesystemencoding()
    esc = 'surrogateescape'
    try:
        ''.encode('utf-8', esc)
    except LookupError:
        esc = 'replace'
    environ = {}

    
    
    
    for k, v in os.environ.items():
        if _needs_transcode(k):

            
            
            if sys.platform == 'win32':
                software = os.environ.get('SERVER_SOFTWARE', '').lower()

                
                
                
                
                
                
                
                if software.startswith('microsoft-iis/'):
                    v = v.encode('utf-8').decode('iso-8859-1')

                
                
                elif software.startswith('apache/'):
                    pass

                
                
                
                elif (
                    software.startswith('simplehttp/')
                    and 'python/3' in software
                ):
                    v = v.encode('utf-8').decode('iso-8859-1')

                
                
                
                else:
                    v = v.encode(enc, 'replace').decode('iso-8859-1')

            
            
            else:
                v = v.encode(enc, esc).decode('iso-8859-1')

        environ[k] = v
    return environ


class BaseHandler:
    

    
    wsgi_version = (1,0)
    wsgi_multithread = True
    wsgi_multiprocess = True
    wsgi_run_once = False

    origin_server = True    
    http_version  = "1.0"   
    server_software = None  

    
    
    
    os_environ= read_environ()

    
    wsgi_file_wrapper = FileWrapper     
    headers_class = Headers             

    
    traceback_limit = None  
    error_status = "500 Internal Server Error"
    error_headers = [('Content-Type','text/plain')]
    error_body = b"A server error occurred.  Please contact the administrator."

    
    status = result = None
    headers_sent = False
    headers = None
    bytes_sent = 0

    def run(self, application):
        
        
        
        
        
        
        try:
            self.setup_environ()
            self.result = application(self.environ, self.start_response)
            self.finish_response()
        except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError):
            
            
            return
        except:
            try:
                self.handle_error()
            except:
                
                self.close()
                raise   


    def setup_environ(self):
        

        env = self.environ = self.os_environ.copy()
        self.add_cgi_vars()

        env['wsgi.input']        = self.get_stdin()
        env['wsgi.errors']       = self.get_stderr()
        env['wsgi.version']      = self.wsgi_version
        env['wsgi.run_once']     = self.wsgi_run_once
        env['wsgi.url_scheme']   = self.get_scheme()
        env['wsgi.multithread']  = self.wsgi_multithread
        env['wsgi.multiprocess'] = self.wsgi_multiprocess

        if self.wsgi_file_wrapper is not None:
            env['wsgi.file_wrapper'] = self.wsgi_file_wrapper

        if self.origin_server and self.server_software:
            env.setdefault('SERVER_SOFTWARE',self.server_software)


    def finish_response(self):
        
        try:
            if not self.result_is_file() or not self.sendfile():
                for data in self.result:
                    self.write(data)
                self.finish_content()
        except:
            
            
            if hasattr(self.result, 'close'):
                self.result.close()
            raise
        else:
            
            
            
            self.close()


    def get_scheme(self):
        
        return guess_scheme(self.environ)


    def set_content_length(self):
        
        try:
            blocks = len(self.result)
        except (TypeError,AttributeError,NotImplementedError):
            pass
        else:
            if blocks==1:
                self.headers['Content-Length'] = str(self.bytes_sent)
                return
        


    def cleanup_headers(self):
        
        if 'Content-Length' not in self.headers:
            self.set_content_length()

    def start_response(self, status, headers,exc_info=None):
        

        if exc_info:
            try:
                if self.headers_sent:
                    raise
            finally:
                exc_info = None        
        elif self.headers is not None:
            raise AssertionError("Headers already set!")

        self.status = status
        self.headers = self.headers_class(headers)
        status = self._convert_string_type(status, "Status")
        self._validate_status(status)

        if __debug__:
            for name, val in headers:
                name = self._convert_string_type(name, "Header name")
                val = self._convert_string_type(val, "Header value")
                assert not is_hop_by_hop(name),\
                       f"Hop-by-hop header, '{name}: {val}', not allowed"

        return self.write

    def _validate_status(self, status):
        if len(status) < 4:
            raise AssertionError("Status must be at least 4 characters")
        if not status[:3].isdigit():
            raise AssertionError("Status message must begin w/3-digit code")
        if status[3] != " ":
            raise AssertionError("Status message must have a space after code")

    def _convert_string_type(self, value, title):
        
        if type(value) is str:
            return value
        raise AssertionError(
            "{0} must be of type str (got {1})".format(title, repr(value))
        )

    def send_preamble(self):
        
        if self.origin_server:
            if self.client_is_modern():
                self._write(('HTTP/%s %s\r\n' % (self.http_version,self.status)).encode('iso-8859-1'))
                if 'Date' not in self.headers:
                    self._write(
                        ('Date: %s\r\n' % format_date_time(time.time())).encode('iso-8859-1')
                    )
                if self.server_software and 'Server' not in self.headers:
                    self._write(('Server: %s\r\n' % self.server_software).encode('iso-8859-1'))
        else:
            self._write(('Status: %s\r\n' % self.status).encode('iso-8859-1'))

    def write(self, data):
        

        assert type(data) is bytes, \
            "write() argument must be a bytes instance"

        if not self.status:
            raise AssertionError("write() before start_response()")

        elif not self.headers_sent:
            
            self.bytes_sent = len(data)    
            self.send_headers()
        else:
            self.bytes_sent += len(data)

        
        self._write(data)
        self._flush()


    def sendfile(self):
        
        return False   


    def finish_content(self):
        
        if not self.headers_sent:
            
            
            self.headers.setdefault('Content-Length', "0")
            self.send_headers()
        else:
            pass 

    def close(self):
        
        try:
            if hasattr(self.result,'close'):
                self.result.close()
        finally:
            self.result = self.headers = self.status = self.environ = None
            self.bytes_sent = 0; self.headers_sent = False


    def send_headers(self):
        
        self.cleanup_headers()
        self.headers_sent = True
        if not self.origin_server or self.client_is_modern():
            self.send_preamble()
            self._write(bytes(self.headers))


    def result_is_file(self):
        
        wrapper = self.wsgi_file_wrapper
        return wrapper is not None and isinstance(self.result,wrapper)


    def client_is_modern(self):
        
        return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9'


    def log_exception(self,exc_info):
        
        try:
            from traceback import print_exception
            stderr = self.get_stderr()
            print_exception(
                exc_info[0], exc_info[1], exc_info[2],
                self.traceback_limit, stderr
            )
            stderr.flush()
        finally:
            exc_info = None

    def handle_error(self):
        
        self.log_exception(sys.exc_info())
        if not self.headers_sent:
            self.result = self.error_output(self.environ, self.start_response)
            self.finish_response()
        

    def error_output(self, environ, start_response):
        
        start_response(self.error_status,self.error_headers[:],sys.exc_info())
        return [self.error_body]


    

    def _write(self,data):
        
        raise NotImplementedError

    def _flush(self):
        
        raise NotImplementedError

    def get_stdin(self):
        
        raise NotImplementedError

    def get_stderr(self):
        
        raise NotImplementedError

    def add_cgi_vars(self):
        
        raise NotImplementedError


class SimpleHandler(BaseHandler):
    

    def __init__(self,stdin,stdout,stderr,environ,
        multithread=True, multiprocess=False
    ):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.base_env = environ
        self.wsgi_multithread = multithread
        self.wsgi_multiprocess = multiprocess

    def get_stdin(self):
        return self.stdin

    def get_stderr(self):
        return self.stderr

    def add_cgi_vars(self):
        self.environ.update(self.base_env)

    def _write(self,data):
        result = self.stdout.write(data)
        if result is None or result == len(data):
            return
        from warnings import warn
        warn("SimpleHandler.stdout.write() should not do partial writes",
            DeprecationWarning)
        while data := data[result:]:
            result = self.stdout.write(data)

    def _flush(self):
        self.stdout.flush()
        self._flush = self.stdout.flush


class BaseCGIHandler(SimpleHandler):

    

    origin_server = False


class CGIHandler(BaseCGIHandler):

    

    wsgi_run_once = True
    
    
    
    os_environ = {}

    def __init__(self):
        BaseCGIHandler.__init__(
            self, sys.stdin.buffer, sys.stdout.buffer, sys.stderr,
            read_environ(), multithread=False, multiprocess=True
        )


class IISCGIHandler(BaseCGIHandler):
    
    wsgi_run_once = True
    os_environ = {}

    
    
    

    
    
    
    
    
    
    

    
    
    def __init__(self):
        environ= read_environ()
        path = environ.get('PATH_INFO', '')
        script = environ.get('SCRIPT_NAME', '')
        if (path+'/').startswith(script+'/'):
            environ['PATH_INFO'] = path[len(script):]
        BaseCGIHandler.__init__(
            self, sys.stdin.buffer, sys.stdout.buffer, sys.stderr,
            environ, multithread=False, multiprocess=True
        )