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