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