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