1*cda5da8dSAndroid Build Coastguard Worker"""Miscellaneous WSGI-related Utilities""" 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard Workerimport posixpath 4*cda5da8dSAndroid Build Coastguard Worker 5*cda5da8dSAndroid Build Coastguard Worker__all__ = [ 6*cda5da8dSAndroid Build Coastguard Worker 'FileWrapper', 'guess_scheme', 'application_uri', 'request_uri', 7*cda5da8dSAndroid Build Coastguard Worker 'shift_path_info', 'setup_testing_defaults', 8*cda5da8dSAndroid Build Coastguard Worker] 9*cda5da8dSAndroid Build Coastguard Worker 10*cda5da8dSAndroid Build Coastguard Worker 11*cda5da8dSAndroid Build Coastguard Workerclass FileWrapper: 12*cda5da8dSAndroid Build Coastguard Worker """Wrapper to convert file-like objects to iterables""" 13*cda5da8dSAndroid Build Coastguard Worker 14*cda5da8dSAndroid Build Coastguard Worker def __init__(self, filelike, blksize=8192): 15*cda5da8dSAndroid Build Coastguard Worker self.filelike = filelike 16*cda5da8dSAndroid Build Coastguard Worker self.blksize = blksize 17*cda5da8dSAndroid Build Coastguard Worker if hasattr(filelike,'close'): 18*cda5da8dSAndroid Build Coastguard Worker self.close = filelike.close 19*cda5da8dSAndroid Build Coastguard Worker 20*cda5da8dSAndroid Build Coastguard Worker def __iter__(self): 21*cda5da8dSAndroid Build Coastguard Worker return self 22*cda5da8dSAndroid Build Coastguard Worker 23*cda5da8dSAndroid Build Coastguard Worker def __next__(self): 24*cda5da8dSAndroid Build Coastguard Worker data = self.filelike.read(self.blksize) 25*cda5da8dSAndroid Build Coastguard Worker if data: 26*cda5da8dSAndroid Build Coastguard Worker return data 27*cda5da8dSAndroid Build Coastguard Worker raise StopIteration 28*cda5da8dSAndroid Build Coastguard Worker 29*cda5da8dSAndroid Build Coastguard Workerdef guess_scheme(environ): 30*cda5da8dSAndroid Build Coastguard Worker """Return a guess for whether 'wsgi.url_scheme' should be 'http' or 'https' 31*cda5da8dSAndroid Build Coastguard Worker """ 32*cda5da8dSAndroid Build Coastguard Worker if environ.get("HTTPS") in ('yes','on','1'): 33*cda5da8dSAndroid Build Coastguard Worker return 'https' 34*cda5da8dSAndroid Build Coastguard Worker else: 35*cda5da8dSAndroid Build Coastguard Worker return 'http' 36*cda5da8dSAndroid Build Coastguard Worker 37*cda5da8dSAndroid Build Coastguard Workerdef application_uri(environ): 38*cda5da8dSAndroid Build Coastguard Worker """Return the application's base URI (no PATH_INFO or QUERY_STRING)""" 39*cda5da8dSAndroid Build Coastguard Worker url = environ['wsgi.url_scheme']+'://' 40*cda5da8dSAndroid Build Coastguard Worker from urllib.parse import quote 41*cda5da8dSAndroid Build Coastguard Worker 42*cda5da8dSAndroid Build Coastguard Worker if environ.get('HTTP_HOST'): 43*cda5da8dSAndroid Build Coastguard Worker url += environ['HTTP_HOST'] 44*cda5da8dSAndroid Build Coastguard Worker else: 45*cda5da8dSAndroid Build Coastguard Worker url += environ['SERVER_NAME'] 46*cda5da8dSAndroid Build Coastguard Worker 47*cda5da8dSAndroid Build Coastguard Worker if environ['wsgi.url_scheme'] == 'https': 48*cda5da8dSAndroid Build Coastguard Worker if environ['SERVER_PORT'] != '443': 49*cda5da8dSAndroid Build Coastguard Worker url += ':' + environ['SERVER_PORT'] 50*cda5da8dSAndroid Build Coastguard Worker else: 51*cda5da8dSAndroid Build Coastguard Worker if environ['SERVER_PORT'] != '80': 52*cda5da8dSAndroid Build Coastguard Worker url += ':' + environ['SERVER_PORT'] 53*cda5da8dSAndroid Build Coastguard Worker 54*cda5da8dSAndroid Build Coastguard Worker url += quote(environ.get('SCRIPT_NAME') or '/', encoding='latin1') 55*cda5da8dSAndroid Build Coastguard Worker return url 56*cda5da8dSAndroid Build Coastguard Worker 57*cda5da8dSAndroid Build Coastguard Workerdef request_uri(environ, include_query=True): 58*cda5da8dSAndroid Build Coastguard Worker """Return the full request URI, optionally including the query string""" 59*cda5da8dSAndroid Build Coastguard Worker url = application_uri(environ) 60*cda5da8dSAndroid Build Coastguard Worker from urllib.parse import quote 61*cda5da8dSAndroid Build Coastguard Worker path_info = quote(environ.get('PATH_INFO',''), safe='/;=,', encoding='latin1') 62*cda5da8dSAndroid Build Coastguard Worker if not environ.get('SCRIPT_NAME'): 63*cda5da8dSAndroid Build Coastguard Worker url += path_info[1:] 64*cda5da8dSAndroid Build Coastguard Worker else: 65*cda5da8dSAndroid Build Coastguard Worker url += path_info 66*cda5da8dSAndroid Build Coastguard Worker if include_query and environ.get('QUERY_STRING'): 67*cda5da8dSAndroid Build Coastguard Worker url += '?' + environ['QUERY_STRING'] 68*cda5da8dSAndroid Build Coastguard Worker return url 69*cda5da8dSAndroid Build Coastguard Worker 70*cda5da8dSAndroid Build Coastguard Workerdef shift_path_info(environ): 71*cda5da8dSAndroid Build Coastguard Worker """Shift a name from PATH_INFO to SCRIPT_NAME, returning it 72*cda5da8dSAndroid Build Coastguard Worker 73*cda5da8dSAndroid Build Coastguard Worker If there are no remaining path segments in PATH_INFO, return None. 74*cda5da8dSAndroid Build Coastguard Worker Note: 'environ' is modified in-place; use a copy if you need to keep 75*cda5da8dSAndroid Build Coastguard Worker the original PATH_INFO or SCRIPT_NAME. 76*cda5da8dSAndroid Build Coastguard Worker 77*cda5da8dSAndroid Build Coastguard Worker Note: when PATH_INFO is just a '/', this returns '' and appends a trailing 78*cda5da8dSAndroid Build Coastguard Worker '/' to SCRIPT_NAME, even though empty path segments are normally ignored, 79*cda5da8dSAndroid Build Coastguard Worker and SCRIPT_NAME doesn't normally end in a '/'. This is intentional 80*cda5da8dSAndroid Build Coastguard Worker behavior, to ensure that an application can tell the difference between 81*cda5da8dSAndroid Build Coastguard Worker '/x' and '/x/' when traversing to objects. 82*cda5da8dSAndroid Build Coastguard Worker """ 83*cda5da8dSAndroid Build Coastguard Worker path_info = environ.get('PATH_INFO','') 84*cda5da8dSAndroid Build Coastguard Worker if not path_info: 85*cda5da8dSAndroid Build Coastguard Worker return None 86*cda5da8dSAndroid Build Coastguard Worker 87*cda5da8dSAndroid Build Coastguard Worker path_parts = path_info.split('/') 88*cda5da8dSAndroid Build Coastguard Worker path_parts[1:-1] = [p for p in path_parts[1:-1] if p and p != '.'] 89*cda5da8dSAndroid Build Coastguard Worker name = path_parts[1] 90*cda5da8dSAndroid Build Coastguard Worker del path_parts[1] 91*cda5da8dSAndroid Build Coastguard Worker 92*cda5da8dSAndroid Build Coastguard Worker script_name = environ.get('SCRIPT_NAME','') 93*cda5da8dSAndroid Build Coastguard Worker script_name = posixpath.normpath(script_name+'/'+name) 94*cda5da8dSAndroid Build Coastguard Worker if script_name.endswith('/'): 95*cda5da8dSAndroid Build Coastguard Worker script_name = script_name[:-1] 96*cda5da8dSAndroid Build Coastguard Worker if not name and not script_name.endswith('/'): 97*cda5da8dSAndroid Build Coastguard Worker script_name += '/' 98*cda5da8dSAndroid Build Coastguard Worker 99*cda5da8dSAndroid Build Coastguard Worker environ['SCRIPT_NAME'] = script_name 100*cda5da8dSAndroid Build Coastguard Worker environ['PATH_INFO'] = '/'.join(path_parts) 101*cda5da8dSAndroid Build Coastguard Worker 102*cda5da8dSAndroid Build Coastguard Worker # Special case: '/.' on PATH_INFO doesn't get stripped, 103*cda5da8dSAndroid Build Coastguard Worker # because we don't strip the last element of PATH_INFO 104*cda5da8dSAndroid Build Coastguard Worker # if there's only one path part left. Instead of fixing this 105*cda5da8dSAndroid Build Coastguard Worker # above, we fix it here so that PATH_INFO gets normalized to 106*cda5da8dSAndroid Build Coastguard Worker # an empty string in the environ. 107*cda5da8dSAndroid Build Coastguard Worker if name=='.': 108*cda5da8dSAndroid Build Coastguard Worker name = None 109*cda5da8dSAndroid Build Coastguard Worker return name 110*cda5da8dSAndroid Build Coastguard Worker 111*cda5da8dSAndroid Build Coastguard Workerdef setup_testing_defaults(environ): 112*cda5da8dSAndroid Build Coastguard Worker """Update 'environ' with trivial defaults for testing purposes 113*cda5da8dSAndroid Build Coastguard Worker 114*cda5da8dSAndroid Build Coastguard Worker This adds various parameters required for WSGI, including HTTP_HOST, 115*cda5da8dSAndroid Build Coastguard Worker SERVER_NAME, SERVER_PORT, REQUEST_METHOD, SCRIPT_NAME, PATH_INFO, 116*cda5da8dSAndroid Build Coastguard Worker and all of the wsgi.* variables. It only supplies default values, 117*cda5da8dSAndroid Build Coastguard Worker and does not replace any existing settings for these variables. 118*cda5da8dSAndroid Build Coastguard Worker 119*cda5da8dSAndroid Build Coastguard Worker This routine is intended to make it easier for unit tests of WSGI 120*cda5da8dSAndroid Build Coastguard Worker servers and applications to set up dummy environments. It should *not* 121*cda5da8dSAndroid Build Coastguard Worker be used by actual WSGI servers or applications, since the data is fake! 122*cda5da8dSAndroid Build Coastguard Worker """ 123*cda5da8dSAndroid Build Coastguard Worker 124*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('SERVER_NAME','127.0.0.1') 125*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('SERVER_PROTOCOL','HTTP/1.0') 126*cda5da8dSAndroid Build Coastguard Worker 127*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('HTTP_HOST',environ['SERVER_NAME']) 128*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('REQUEST_METHOD','GET') 129*cda5da8dSAndroid Build Coastguard Worker 130*cda5da8dSAndroid Build Coastguard Worker if 'SCRIPT_NAME' not in environ and 'PATH_INFO' not in environ: 131*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('SCRIPT_NAME','') 132*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('PATH_INFO','/') 133*cda5da8dSAndroid Build Coastguard Worker 134*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('wsgi.version', (1,0)) 135*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('wsgi.run_once', 0) 136*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('wsgi.multithread', 0) 137*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('wsgi.multiprocess', 0) 138*cda5da8dSAndroid Build Coastguard Worker 139*cda5da8dSAndroid Build Coastguard Worker from io import StringIO, BytesIO 140*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('wsgi.input', BytesIO()) 141*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('wsgi.errors', StringIO()) 142*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('wsgi.url_scheme',guess_scheme(environ)) 143*cda5da8dSAndroid Build Coastguard Worker 144*cda5da8dSAndroid Build Coastguard Worker if environ['wsgi.url_scheme']=='http': 145*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('SERVER_PORT', '80') 146*cda5da8dSAndroid Build Coastguard Worker elif environ['wsgi.url_scheme']=='https': 147*cda5da8dSAndroid Build Coastguard Worker environ.setdefault('SERVER_PORT', '443') 148*cda5da8dSAndroid Build Coastguard Worker 149*cda5da8dSAndroid Build Coastguard Worker 150*cda5da8dSAndroid Build Coastguard Worker 151*cda5da8dSAndroid Build Coastguard Worker_hoppish = { 152*cda5da8dSAndroid Build Coastguard Worker 'connection', 'keep-alive', 'proxy-authenticate', 153*cda5da8dSAndroid Build Coastguard Worker 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 154*cda5da8dSAndroid Build Coastguard Worker 'upgrade' 155*cda5da8dSAndroid Build Coastguard Worker}.__contains__ 156*cda5da8dSAndroid Build Coastguard Worker 157*cda5da8dSAndroid Build Coastguard Workerdef is_hop_by_hop(header_name): 158*cda5da8dSAndroid Build Coastguard Worker """Return true if 'header_name' is an HTTP/1.1 "Hop-by-Hop" header""" 159*cda5da8dSAndroid Build Coastguard Worker return _hoppish(header_name.lower()) 160