##// END OF EJS Templates
hgweb: teach WSGI parser about query strings...
Gregory Szorc -
r36827:3c15b84a default
parent child Browse files
Show More
@@ -1,279 +1,296 b''
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import cgi
11 import cgi
12 import errno
12 import errno
13 import socket
13 import socket
14 #import wsgiref.validate
14 #import wsgiref.validate
15
15
16 from .common import (
16 from .common import (
17 ErrorResponse,
17 ErrorResponse,
18 HTTP_NOT_MODIFIED,
18 HTTP_NOT_MODIFIED,
19 statusmessage,
19 statusmessage,
20 )
20 )
21
21
22 from ..thirdparty import (
22 from ..thirdparty import (
23 attr,
23 attr,
24 )
24 )
25 from .. import (
25 from .. import (
26 pycompat,
26 pycompat,
27 util,
27 util,
28 )
28 )
29
29
30 shortcuts = {
30 shortcuts = {
31 'cl': [('cmd', ['changelog']), ('rev', None)],
31 'cl': [('cmd', ['changelog']), ('rev', None)],
32 'sl': [('cmd', ['shortlog']), ('rev', None)],
32 'sl': [('cmd', ['shortlog']), ('rev', None)],
33 'cs': [('cmd', ['changeset']), ('node', None)],
33 'cs': [('cmd', ['changeset']), ('node', None)],
34 'f': [('cmd', ['file']), ('filenode', None)],
34 'f': [('cmd', ['file']), ('filenode', None)],
35 'fl': [('cmd', ['filelog']), ('filenode', None)],
35 'fl': [('cmd', ['filelog']), ('filenode', None)],
36 'fd': [('cmd', ['filediff']), ('node', None)],
36 'fd': [('cmd', ['filediff']), ('node', None)],
37 'fa': [('cmd', ['annotate']), ('filenode', None)],
37 'fa': [('cmd', ['annotate']), ('filenode', None)],
38 'mf': [('cmd', ['manifest']), ('manifest', None)],
38 'mf': [('cmd', ['manifest']), ('manifest', None)],
39 'ca': [('cmd', ['archive']), ('node', None)],
39 'ca': [('cmd', ['archive']), ('node', None)],
40 'tags': [('cmd', ['tags'])],
40 'tags': [('cmd', ['tags'])],
41 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
41 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
42 'static': [('cmd', ['static']), ('file', None)]
42 'static': [('cmd', ['static']), ('file', None)]
43 }
43 }
44
44
45 def normalize(form):
45 def normalize(form):
46 # first expand the shortcuts
46 # first expand the shortcuts
47 for k in shortcuts:
47 for k in shortcuts:
48 if k in form:
48 if k in form:
49 for name, value in shortcuts[k]:
49 for name, value in shortcuts[k]:
50 if value is None:
50 if value is None:
51 value = form[k]
51 value = form[k]
52 form[name] = value
52 form[name] = value
53 del form[k]
53 del form[k]
54 # And strip the values
54 # And strip the values
55 bytesform = {}
55 bytesform = {}
56 for k, v in form.iteritems():
56 for k, v in form.iteritems():
57 bytesform[pycompat.bytesurl(k)] = [
57 bytesform[pycompat.bytesurl(k)] = [
58 pycompat.bytesurl(i.strip()) for i in v]
58 pycompat.bytesurl(i.strip()) for i in v]
59 return bytesform
59 return bytesform
60
60
61 @attr.s(frozen=True)
61 @attr.s(frozen=True)
62 class parsedrequest(object):
62 class parsedrequest(object):
63 """Represents a parsed WSGI request / static HTTP request parameters."""
63 """Represents a parsed WSGI request / static HTTP request parameters."""
64
64
65 # Full URL for this request.
65 # Full URL for this request.
66 url = attr.ib()
66 url = attr.ib()
67 # URL without any path components. Just <proto>://<host><port>.
67 # URL without any path components. Just <proto>://<host><port>.
68 baseurl = attr.ib()
68 baseurl = attr.ib()
69 # Advertised URL. Like ``url`` and ``baseurl`` but uses SERVER_NAME instead
69 # Advertised URL. Like ``url`` and ``baseurl`` but uses SERVER_NAME instead
70 # of HTTP: Host header for hostname. This is likely what clients used.
70 # of HTTP: Host header for hostname. This is likely what clients used.
71 advertisedurl = attr.ib()
71 advertisedurl = attr.ib()
72 advertisedbaseurl = attr.ib()
72 advertisedbaseurl = attr.ib()
73 # WSGI application path.
73 # WSGI application path.
74 apppath = attr.ib()
74 apppath = attr.ib()
75 # List of path parts to be used for dispatch.
75 # List of path parts to be used for dispatch.
76 dispatchparts = attr.ib()
76 dispatchparts = attr.ib()
77 # URL path component (no query string) used for dispatch.
77 # URL path component (no query string) used for dispatch.
78 dispatchpath = attr.ib()
78 dispatchpath = attr.ib()
79 # Raw query string (part after "?" in URL).
79 # Raw query string (part after "?" in URL).
80 querystring = attr.ib()
80 querystring = attr.ib()
81 # List of 2-tuples of query string arguments.
82 querystringlist = attr.ib()
83 # Dict of query string arguments. Values are lists with at least 1 item.
84 querystringdict = attr.ib()
81
85
82 def parserequestfromenv(env):
86 def parserequestfromenv(env):
83 """Parse URL components from environment variables.
87 """Parse URL components from environment variables.
84
88
85 WSGI defines request attributes via environment variables. This function
89 WSGI defines request attributes via environment variables. This function
86 parses the environment variables into a data structure.
90 parses the environment variables into a data structure.
87 """
91 """
88 # PEP-0333 defines the WSGI spec and is a useful reference for this code.
92 # PEP-0333 defines the WSGI spec and is a useful reference for this code.
89
93
90 # We first validate that the incoming object conforms with the WSGI spec.
94 # We first validate that the incoming object conforms with the WSGI spec.
91 # We only want to be dealing with spec-conforming WSGI implementations.
95 # We only want to be dealing with spec-conforming WSGI implementations.
92 # TODO enable this once we fix internal violations.
96 # TODO enable this once we fix internal violations.
93 #wsgiref.validate.check_environ(env)
97 #wsgiref.validate.check_environ(env)
94
98
95 # PEP-0333 states that environment keys and values are native strings
99 # PEP-0333 states that environment keys and values are native strings
96 # (bytes on Python 2 and str on Python 3). The code points for the Unicode
100 # (bytes on Python 2 and str on Python 3). The code points for the Unicode
97 # strings on Python 3 must be between \00000-\000FF. We deal with bytes
101 # strings on Python 3 must be between \00000-\000FF. We deal with bytes
98 # in Mercurial, so mass convert string keys and values to bytes.
102 # in Mercurial, so mass convert string keys and values to bytes.
99 if pycompat.ispy3:
103 if pycompat.ispy3:
100 env = {k.encode('latin-1'): v for k, v in env.iteritems()}
104 env = {k.encode('latin-1'): v for k, v in env.iteritems()}
101 env = {k: v.encode('latin-1') if isinstance(v, str) else v
105 env = {k: v.encode('latin-1') if isinstance(v, str) else v
102 for k, v in env.iteritems()}
106 for k, v in env.iteritems()}
103
107
104 # https://www.python.org/dev/peps/pep-0333/#environ-variables defines
108 # https://www.python.org/dev/peps/pep-0333/#environ-variables defines
105 # the environment variables.
109 # the environment variables.
106 # https://www.python.org/dev/peps/pep-0333/#url-reconstruction defines
110 # https://www.python.org/dev/peps/pep-0333/#url-reconstruction defines
107 # how URLs are reconstructed.
111 # how URLs are reconstructed.
108 fullurl = env['wsgi.url_scheme'] + '://'
112 fullurl = env['wsgi.url_scheme'] + '://'
109 advertisedfullurl = fullurl
113 advertisedfullurl = fullurl
110
114
111 def addport(s):
115 def addport(s):
112 if env['wsgi.url_scheme'] == 'https':
116 if env['wsgi.url_scheme'] == 'https':
113 if env['SERVER_PORT'] != '443':
117 if env['SERVER_PORT'] != '443':
114 s += ':' + env['SERVER_PORT']
118 s += ':' + env['SERVER_PORT']
115 else:
119 else:
116 if env['SERVER_PORT'] != '80':
120 if env['SERVER_PORT'] != '80':
117 s += ':' + env['SERVER_PORT']
121 s += ':' + env['SERVER_PORT']
118
122
119 return s
123 return s
120
124
121 if env.get('HTTP_HOST'):
125 if env.get('HTTP_HOST'):
122 fullurl += env['HTTP_HOST']
126 fullurl += env['HTTP_HOST']
123 else:
127 else:
124 fullurl += env['SERVER_NAME']
128 fullurl += env['SERVER_NAME']
125 fullurl = addport(fullurl)
129 fullurl = addport(fullurl)
126
130
127 advertisedfullurl += env['SERVER_NAME']
131 advertisedfullurl += env['SERVER_NAME']
128 advertisedfullurl = addport(advertisedfullurl)
132 advertisedfullurl = addport(advertisedfullurl)
129
133
130 baseurl = fullurl
134 baseurl = fullurl
131 advertisedbaseurl = advertisedfullurl
135 advertisedbaseurl = advertisedfullurl
132
136
133 fullurl += util.urlreq.quote(env.get('SCRIPT_NAME', ''))
137 fullurl += util.urlreq.quote(env.get('SCRIPT_NAME', ''))
134 advertisedfullurl += util.urlreq.quote(env.get('SCRIPT_NAME', ''))
138 advertisedfullurl += util.urlreq.quote(env.get('SCRIPT_NAME', ''))
135 fullurl += util.urlreq.quote(env.get('PATH_INFO', ''))
139 fullurl += util.urlreq.quote(env.get('PATH_INFO', ''))
136 advertisedfullurl += util.urlreq.quote(env.get('PATH_INFO', ''))
140 advertisedfullurl += util.urlreq.quote(env.get('PATH_INFO', ''))
137
141
138 if env.get('QUERY_STRING'):
142 if env.get('QUERY_STRING'):
139 fullurl += '?' + env['QUERY_STRING']
143 fullurl += '?' + env['QUERY_STRING']
140 advertisedfullurl += '?' + env['QUERY_STRING']
144 advertisedfullurl += '?' + env['QUERY_STRING']
141
145
142 # When dispatching requests, we look at the URL components (PATH_INFO
146 # When dispatching requests, we look at the URL components (PATH_INFO
143 # and QUERY_STRING) after the application root (SCRIPT_NAME). But hgwebdir
147 # and QUERY_STRING) after the application root (SCRIPT_NAME). But hgwebdir
144 # has the concept of "virtual" repositories. This is defined via REPO_NAME.
148 # has the concept of "virtual" repositories. This is defined via REPO_NAME.
145 # If REPO_NAME is defined, we append it to SCRIPT_NAME to form a new app
149 # If REPO_NAME is defined, we append it to SCRIPT_NAME to form a new app
146 # root. We also exclude its path components from PATH_INFO when resolving
150 # root. We also exclude its path components from PATH_INFO when resolving
147 # the dispatch path.
151 # the dispatch path.
148
152
149 apppath = env['SCRIPT_NAME']
153 apppath = env['SCRIPT_NAME']
150
154
151 if env.get('REPO_NAME'):
155 if env.get('REPO_NAME'):
152 if not apppath.endswith('/'):
156 if not apppath.endswith('/'):
153 apppath += '/'
157 apppath += '/'
154
158
155 apppath += env.get('REPO_NAME')
159 apppath += env.get('REPO_NAME')
156
160
157 if 'PATH_INFO' in env:
161 if 'PATH_INFO' in env:
158 dispatchparts = env['PATH_INFO'].strip('/').split('/')
162 dispatchparts = env['PATH_INFO'].strip('/').split('/')
159
163
160 # Strip out repo parts.
164 # Strip out repo parts.
161 repoparts = env.get('REPO_NAME', '').split('/')
165 repoparts = env.get('REPO_NAME', '').split('/')
162 if dispatchparts[:len(repoparts)] == repoparts:
166 if dispatchparts[:len(repoparts)] == repoparts:
163 dispatchparts = dispatchparts[len(repoparts):]
167 dispatchparts = dispatchparts[len(repoparts):]
164 else:
168 else:
165 dispatchparts = []
169 dispatchparts = []
166
170
167 dispatchpath = '/'.join(dispatchparts)
171 dispatchpath = '/'.join(dispatchparts)
168
172
169 querystring = env.get('QUERY_STRING', '')
173 querystring = env.get('QUERY_STRING', '')
170
174
175 # We store as a list so we have ordering information. We also store as
176 # a dict to facilitate fast lookup.
177 querystringlist = util.urlreq.parseqsl(querystring, keep_blank_values=True)
178
179 querystringdict = {}
180 for k, v in querystringlist:
181 if k in querystringdict:
182 querystringdict[k].append(v)
183 else:
184 querystringdict[k] = [v]
185
171 return parsedrequest(url=fullurl, baseurl=baseurl,
186 return parsedrequest(url=fullurl, baseurl=baseurl,
172 advertisedurl=advertisedfullurl,
187 advertisedurl=advertisedfullurl,
173 advertisedbaseurl=advertisedbaseurl,
188 advertisedbaseurl=advertisedbaseurl,
174 apppath=apppath,
189 apppath=apppath,
175 dispatchparts=dispatchparts, dispatchpath=dispatchpath,
190 dispatchparts=dispatchparts, dispatchpath=dispatchpath,
176 querystring=querystring)
191 querystring=querystring,
192 querystringlist=querystringlist,
193 querystringdict=querystringdict)
177
194
178 class wsgirequest(object):
195 class wsgirequest(object):
179 """Higher-level API for a WSGI request.
196 """Higher-level API for a WSGI request.
180
197
181 WSGI applications are invoked with 2 arguments. They are used to
198 WSGI applications are invoked with 2 arguments. They are used to
182 instantiate instances of this class, which provides higher-level APIs
199 instantiate instances of this class, which provides higher-level APIs
183 for obtaining request parameters, writing HTTP output, etc.
200 for obtaining request parameters, writing HTTP output, etc.
184 """
201 """
185 def __init__(self, wsgienv, start_response):
202 def __init__(self, wsgienv, start_response):
186 version = wsgienv[r'wsgi.version']
203 version = wsgienv[r'wsgi.version']
187 if (version < (1, 0)) or (version >= (2, 0)):
204 if (version < (1, 0)) or (version >= (2, 0)):
188 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
205 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
189 % version)
206 % version)
190 self.inp = wsgienv[r'wsgi.input']
207 self.inp = wsgienv[r'wsgi.input']
191 self.err = wsgienv[r'wsgi.errors']
208 self.err = wsgienv[r'wsgi.errors']
192 self.threaded = wsgienv[r'wsgi.multithread']
209 self.threaded = wsgienv[r'wsgi.multithread']
193 self.multiprocess = wsgienv[r'wsgi.multiprocess']
210 self.multiprocess = wsgienv[r'wsgi.multiprocess']
194 self.run_once = wsgienv[r'wsgi.run_once']
211 self.run_once = wsgienv[r'wsgi.run_once']
195 self.env = wsgienv
212 self.env = wsgienv
196 self.form = normalize(cgi.parse(self.inp,
213 self.form = normalize(cgi.parse(self.inp,
197 self.env,
214 self.env,
198 keep_blank_values=1))
215 keep_blank_values=1))
199 self._start_response = start_response
216 self._start_response = start_response
200 self.server_write = None
217 self.server_write = None
201 self.headers = []
218 self.headers = []
202
219
203 def __iter__(self):
220 def __iter__(self):
204 return iter([])
221 return iter([])
205
222
206 def read(self, count=-1):
223 def read(self, count=-1):
207 return self.inp.read(count)
224 return self.inp.read(count)
208
225
209 def drain(self):
226 def drain(self):
210 '''need to read all data from request, httplib is half-duplex'''
227 '''need to read all data from request, httplib is half-duplex'''
211 length = int(self.env.get('CONTENT_LENGTH') or 0)
228 length = int(self.env.get('CONTENT_LENGTH') or 0)
212 for s in util.filechunkiter(self.inp, limit=length):
229 for s in util.filechunkiter(self.inp, limit=length):
213 pass
230 pass
214
231
215 def respond(self, status, type, filename=None, body=None):
232 def respond(self, status, type, filename=None, body=None):
216 if not isinstance(type, str):
233 if not isinstance(type, str):
217 type = pycompat.sysstr(type)
234 type = pycompat.sysstr(type)
218 if self._start_response is not None:
235 if self._start_response is not None:
219 self.headers.append((r'Content-Type', type))
236 self.headers.append((r'Content-Type', type))
220 if filename:
237 if filename:
221 filename = (filename.rpartition('/')[-1]
238 filename = (filename.rpartition('/')[-1]
222 .replace('\\', '\\\\').replace('"', '\\"'))
239 .replace('\\', '\\\\').replace('"', '\\"'))
223 self.headers.append(('Content-Disposition',
240 self.headers.append(('Content-Disposition',
224 'inline; filename="%s"' % filename))
241 'inline; filename="%s"' % filename))
225 if body is not None:
242 if body is not None:
226 self.headers.append((r'Content-Length', str(len(body))))
243 self.headers.append((r'Content-Length', str(len(body))))
227
244
228 for k, v in self.headers:
245 for k, v in self.headers:
229 if not isinstance(v, str):
246 if not isinstance(v, str):
230 raise TypeError('header value must be string: %r' % (v,))
247 raise TypeError('header value must be string: %r' % (v,))
231
248
232 if isinstance(status, ErrorResponse):
249 if isinstance(status, ErrorResponse):
233 self.headers.extend(status.headers)
250 self.headers.extend(status.headers)
234 if status.code == HTTP_NOT_MODIFIED:
251 if status.code == HTTP_NOT_MODIFIED:
235 # RFC 2616 Section 10.3.5: 304 Not Modified has cases where
252 # RFC 2616 Section 10.3.5: 304 Not Modified has cases where
236 # it MUST NOT include any headers other than these and no
253 # it MUST NOT include any headers other than these and no
237 # body
254 # body
238 self.headers = [(k, v) for (k, v) in self.headers if
255 self.headers = [(k, v) for (k, v) in self.headers if
239 k in ('Date', 'ETag', 'Expires',
256 k in ('Date', 'ETag', 'Expires',
240 'Cache-Control', 'Vary')]
257 'Cache-Control', 'Vary')]
241 status = statusmessage(status.code, pycompat.bytestr(status))
258 status = statusmessage(status.code, pycompat.bytestr(status))
242 elif status == 200:
259 elif status == 200:
243 status = '200 Script output follows'
260 status = '200 Script output follows'
244 elif isinstance(status, int):
261 elif isinstance(status, int):
245 status = statusmessage(status)
262 status = statusmessage(status)
246
263
247 self.server_write = self._start_response(
264 self.server_write = self._start_response(
248 pycompat.sysstr(status), self.headers)
265 pycompat.sysstr(status), self.headers)
249 self._start_response = None
266 self._start_response = None
250 self.headers = []
267 self.headers = []
251 if body is not None:
268 if body is not None:
252 self.write(body)
269 self.write(body)
253 self.server_write = None
270 self.server_write = None
254
271
255 def write(self, thing):
272 def write(self, thing):
256 if thing:
273 if thing:
257 try:
274 try:
258 self.server_write(thing)
275 self.server_write(thing)
259 except socket.error as inst:
276 except socket.error as inst:
260 if inst[0] != errno.ECONNRESET:
277 if inst[0] != errno.ECONNRESET:
261 raise
278 raise
262
279
263 def writelines(self, lines):
280 def writelines(self, lines):
264 for line in lines:
281 for line in lines:
265 self.write(line)
282 self.write(line)
266
283
267 def flush(self):
284 def flush(self):
268 return None
285 return None
269
286
270 def close(self):
287 def close(self):
271 return None
288 return None
272
289
273 def wsgiapplication(app_maker):
290 def wsgiapplication(app_maker):
274 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
291 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
275 can and should now be used as a WSGI application.'''
292 can and should now be used as a WSGI application.'''
276 application = app_maker()
293 application = app_maker()
277 def run_wsgi(env, respond):
294 def run_wsgi(env, respond):
278 return application(env, respond)
295 return application(env, respond)
279 return run_wsgi
296 return run_wsgi
@@ -1,190 +1,192 b''
1 # urllibcompat.py - adapters to ease using urllib2 on Py2 and urllib on Py3
1 # urllibcompat.py - adapters to ease using urllib2 on Py2 and urllib on Py3
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 from . import pycompat
9 from . import pycompat
10
10
11 _sysstr = pycompat.sysstr
11 _sysstr = pycompat.sysstr
12
12
13 class _pycompatstub(object):
13 class _pycompatstub(object):
14 def __init__(self):
14 def __init__(self):
15 self._aliases = {}
15 self._aliases = {}
16
16
17 def _registeraliases(self, origin, items):
17 def _registeraliases(self, origin, items):
18 """Add items that will be populated at the first access"""
18 """Add items that will be populated at the first access"""
19 items = map(_sysstr, items)
19 items = map(_sysstr, items)
20 self._aliases.update(
20 self._aliases.update(
21 (item.replace(_sysstr('_'), _sysstr('')).lower(), (origin, item))
21 (item.replace(_sysstr('_'), _sysstr('')).lower(), (origin, item))
22 for item in items)
22 for item in items)
23
23
24 def _registeralias(self, origin, attr, name):
24 def _registeralias(self, origin, attr, name):
25 """Alias ``origin``.``attr`` as ``name``"""
25 """Alias ``origin``.``attr`` as ``name``"""
26 self._aliases[_sysstr(name)] = (origin, _sysstr(attr))
26 self._aliases[_sysstr(name)] = (origin, _sysstr(attr))
27
27
28 def __getattr__(self, name):
28 def __getattr__(self, name):
29 try:
29 try:
30 origin, item = self._aliases[name]
30 origin, item = self._aliases[name]
31 except KeyError:
31 except KeyError:
32 raise AttributeError(name)
32 raise AttributeError(name)
33 self.__dict__[name] = obj = getattr(origin, item)
33 self.__dict__[name] = obj = getattr(origin, item)
34 return obj
34 return obj
35
35
36 httpserver = _pycompatstub()
36 httpserver = _pycompatstub()
37 urlreq = _pycompatstub()
37 urlreq = _pycompatstub()
38 urlerr = _pycompatstub()
38 urlerr = _pycompatstub()
39
39
40 if pycompat.ispy3:
40 if pycompat.ispy3:
41 import urllib.parse
41 import urllib.parse
42 urlreq._registeraliases(urllib.parse, (
42 urlreq._registeraliases(urllib.parse, (
43 "splitattr",
43 "splitattr",
44 "splitpasswd",
44 "splitpasswd",
45 "splitport",
45 "splitport",
46 "splituser",
46 "splituser",
47 "urlparse",
47 "urlparse",
48 "urlunparse",
48 "urlunparse",
49 ))
49 ))
50 urlreq._registeralias(urllib.parse, "parse_qs", "parseqs")
50 urlreq._registeralias(urllib.parse, "parse_qs", "parseqs")
51 urlreq._registeralias(urllib.parse, "parse_qsl", "parseqsl")
51 urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
52 urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
52 import urllib.request
53 import urllib.request
53 urlreq._registeraliases(urllib.request, (
54 urlreq._registeraliases(urllib.request, (
54 "AbstractHTTPHandler",
55 "AbstractHTTPHandler",
55 "BaseHandler",
56 "BaseHandler",
56 "build_opener",
57 "build_opener",
57 "FileHandler",
58 "FileHandler",
58 "FTPHandler",
59 "FTPHandler",
59 "ftpwrapper",
60 "ftpwrapper",
60 "HTTPHandler",
61 "HTTPHandler",
61 "HTTPSHandler",
62 "HTTPSHandler",
62 "install_opener",
63 "install_opener",
63 "pathname2url",
64 "pathname2url",
64 "HTTPBasicAuthHandler",
65 "HTTPBasicAuthHandler",
65 "HTTPDigestAuthHandler",
66 "HTTPDigestAuthHandler",
66 "HTTPPasswordMgrWithDefaultRealm",
67 "HTTPPasswordMgrWithDefaultRealm",
67 "ProxyHandler",
68 "ProxyHandler",
68 "Request",
69 "Request",
69 "url2pathname",
70 "url2pathname",
70 "urlopen",
71 "urlopen",
71 ))
72 ))
72 import urllib.response
73 import urllib.response
73 urlreq._registeraliases(urllib.response, (
74 urlreq._registeraliases(urllib.response, (
74 "addclosehook",
75 "addclosehook",
75 "addinfourl",
76 "addinfourl",
76 ))
77 ))
77 import urllib.error
78 import urllib.error
78 urlerr._registeraliases(urllib.error, (
79 urlerr._registeraliases(urllib.error, (
79 "HTTPError",
80 "HTTPError",
80 "URLError",
81 "URLError",
81 ))
82 ))
82 import http.server
83 import http.server
83 httpserver._registeraliases(http.server, (
84 httpserver._registeraliases(http.server, (
84 "HTTPServer",
85 "HTTPServer",
85 "BaseHTTPRequestHandler",
86 "BaseHTTPRequestHandler",
86 "SimpleHTTPRequestHandler",
87 "SimpleHTTPRequestHandler",
87 "CGIHTTPRequestHandler",
88 "CGIHTTPRequestHandler",
88 ))
89 ))
89
90
90 # urllib.parse.quote() accepts both str and bytes, decodes bytes
91 # urllib.parse.quote() accepts both str and bytes, decodes bytes
91 # (if necessary), and returns str. This is wonky. We provide a custom
92 # (if necessary), and returns str. This is wonky. We provide a custom
92 # implementation that only accepts bytes and emits bytes.
93 # implementation that only accepts bytes and emits bytes.
93 def quote(s, safe=r'/'):
94 def quote(s, safe=r'/'):
94 s = urllib.parse.quote_from_bytes(s, safe=safe)
95 s = urllib.parse.quote_from_bytes(s, safe=safe)
95 return s.encode('ascii', 'strict')
96 return s.encode('ascii', 'strict')
96
97
97 # urllib.parse.urlencode() returns str. We use this function to make
98 # urllib.parse.urlencode() returns str. We use this function to make
98 # sure we return bytes.
99 # sure we return bytes.
99 def urlencode(query, doseq=False):
100 def urlencode(query, doseq=False):
100 s = urllib.parse.urlencode(query, doseq=doseq)
101 s = urllib.parse.urlencode(query, doseq=doseq)
101 return s.encode('ascii')
102 return s.encode('ascii')
102
103
103 urlreq.quote = quote
104 urlreq.quote = quote
104 urlreq.urlencode = urlencode
105 urlreq.urlencode = urlencode
105
106
106 def getfullurl(req):
107 def getfullurl(req):
107 return req.full_url
108 return req.full_url
108
109
109 def gethost(req):
110 def gethost(req):
110 return req.host
111 return req.host
111
112
112 def getselector(req):
113 def getselector(req):
113 return req.selector
114 return req.selector
114
115
115 def getdata(req):
116 def getdata(req):
116 return req.data
117 return req.data
117
118
118 def hasdata(req):
119 def hasdata(req):
119 return req.data is not None
120 return req.data is not None
120 else:
121 else:
121 import BaseHTTPServer
122 import BaseHTTPServer
122 import CGIHTTPServer
123 import CGIHTTPServer
123 import SimpleHTTPServer
124 import SimpleHTTPServer
124 import urllib2
125 import urllib2
125 import urllib
126 import urllib
126 import urlparse
127 import urlparse
127 urlreq._registeraliases(urllib, (
128 urlreq._registeraliases(urllib, (
128 "addclosehook",
129 "addclosehook",
129 "addinfourl",
130 "addinfourl",
130 "ftpwrapper",
131 "ftpwrapper",
131 "pathname2url",
132 "pathname2url",
132 "quote",
133 "quote",
133 "splitattr",
134 "splitattr",
134 "splitpasswd",
135 "splitpasswd",
135 "splitport",
136 "splitport",
136 "splituser",
137 "splituser",
137 "unquote",
138 "unquote",
138 "url2pathname",
139 "url2pathname",
139 "urlencode",
140 "urlencode",
140 ))
141 ))
141 urlreq._registeraliases(urllib2, (
142 urlreq._registeraliases(urllib2, (
142 "AbstractHTTPHandler",
143 "AbstractHTTPHandler",
143 "BaseHandler",
144 "BaseHandler",
144 "build_opener",
145 "build_opener",
145 "FileHandler",
146 "FileHandler",
146 "FTPHandler",
147 "FTPHandler",
147 "HTTPBasicAuthHandler",
148 "HTTPBasicAuthHandler",
148 "HTTPDigestAuthHandler",
149 "HTTPDigestAuthHandler",
149 "HTTPHandler",
150 "HTTPHandler",
150 "HTTPPasswordMgrWithDefaultRealm",
151 "HTTPPasswordMgrWithDefaultRealm",
151 "HTTPSHandler",
152 "HTTPSHandler",
152 "install_opener",
153 "install_opener",
153 "ProxyHandler",
154 "ProxyHandler",
154 "Request",
155 "Request",
155 "urlopen",
156 "urlopen",
156 ))
157 ))
157 urlreq._registeraliases(urlparse, (
158 urlreq._registeraliases(urlparse, (
158 "urlparse",
159 "urlparse",
159 "urlunparse",
160 "urlunparse",
160 ))
161 ))
161 urlreq._registeralias(urlparse, "parse_qs", "parseqs")
162 urlreq._registeralias(urlparse, "parse_qs", "parseqs")
163 urlreq._registeralias(urlparse, "parse_qsl", "parseqsl")
162 urlerr._registeraliases(urllib2, (
164 urlerr._registeraliases(urllib2, (
163 "HTTPError",
165 "HTTPError",
164 "URLError",
166 "URLError",
165 ))
167 ))
166 httpserver._registeraliases(BaseHTTPServer, (
168 httpserver._registeraliases(BaseHTTPServer, (
167 "HTTPServer",
169 "HTTPServer",
168 "BaseHTTPRequestHandler",
170 "BaseHTTPRequestHandler",
169 ))
171 ))
170 httpserver._registeraliases(SimpleHTTPServer, (
172 httpserver._registeraliases(SimpleHTTPServer, (
171 "SimpleHTTPRequestHandler",
173 "SimpleHTTPRequestHandler",
172 ))
174 ))
173 httpserver._registeraliases(CGIHTTPServer, (
175 httpserver._registeraliases(CGIHTTPServer, (
174 "CGIHTTPRequestHandler",
176 "CGIHTTPRequestHandler",
175 ))
177 ))
176
178
177 def gethost(req):
179 def gethost(req):
178 return req.get_host()
180 return req.get_host()
179
181
180 def getselector(req):
182 def getselector(req):
181 return req.get_selector()
183 return req.get_selector()
182
184
183 def getfullurl(req):
185 def getfullurl(req):
184 return req.get_full_url()
186 return req.get_full_url()
185
187
186 def getdata(req):
188 def getdata(req):
187 return req.get_data()
189 return req.get_data()
188
190
189 def hasdata(req):
191 def hasdata(req):
190 return req.has_data()
192 return req.has_data()
General Comments 0
You need to be logged in to leave comments. Login now