Show More
@@ -28,6 +28,90 b' from .. import (' | |||||
28 | util, |
|
28 | util, | |
29 | ) |
|
29 | ) | |
30 |
|
30 | |||
|
31 | class multidict(object): | |||
|
32 | """A dict like object that can store multiple values for a key. | |||
|
33 | ||||
|
34 | Used to store parsed request parameters. | |||
|
35 | ||||
|
36 | This is inspired by WebOb's class of the same name. | |||
|
37 | """ | |||
|
38 | def __init__(self): | |||
|
39 | # Stores (key, value) 2-tuples. This isn't the most efficient. But we | |||
|
40 | # don't rely on parameters that much, so it shouldn't be a perf issue. | |||
|
41 | # we can always add dict for fast lookups. | |||
|
42 | self._items = [] | |||
|
43 | ||||
|
44 | def __getitem__(self, key): | |||
|
45 | """Returns the last set value for a key.""" | |||
|
46 | for k, v in reversed(self._items): | |||
|
47 | if k == key: | |||
|
48 | return v | |||
|
49 | ||||
|
50 | raise KeyError(key) | |||
|
51 | ||||
|
52 | def __setitem__(self, key, value): | |||
|
53 | """Replace a values for a key with a new value.""" | |||
|
54 | try: | |||
|
55 | del self[key] | |||
|
56 | except KeyError: | |||
|
57 | pass | |||
|
58 | ||||
|
59 | self._items.append((key, value)) | |||
|
60 | ||||
|
61 | def __delitem__(self, key): | |||
|
62 | """Delete all values for a key.""" | |||
|
63 | oldlen = len(self._items) | |||
|
64 | ||||
|
65 | self._items[:] = [(k, v) for k, v in self._items if k != key] | |||
|
66 | ||||
|
67 | if oldlen == len(self._items): | |||
|
68 | raise KeyError(key) | |||
|
69 | ||||
|
70 | def __contains__(self, key): | |||
|
71 | return any(k == key for k, v in self._items) | |||
|
72 | ||||
|
73 | def __len__(self): | |||
|
74 | return len(self._items) | |||
|
75 | ||||
|
76 | def get(self, key, default=None): | |||
|
77 | try: | |||
|
78 | return self.__getitem__(key) | |||
|
79 | except KeyError: | |||
|
80 | return default | |||
|
81 | ||||
|
82 | def add(self, key, value): | |||
|
83 | """Add a new value for a key. Does not replace existing values.""" | |||
|
84 | self._items.append((key, value)) | |||
|
85 | ||||
|
86 | def getall(self, key): | |||
|
87 | """Obtains all values for a key.""" | |||
|
88 | return [v for k, v in self._items if k == key] | |||
|
89 | ||||
|
90 | def getone(self, key): | |||
|
91 | """Obtain a single value for a key. | |||
|
92 | ||||
|
93 | Raises KeyError if key not defined or it has multiple values set. | |||
|
94 | """ | |||
|
95 | vals = self.getall(key) | |||
|
96 | ||||
|
97 | if not vals: | |||
|
98 | raise KeyError(key) | |||
|
99 | ||||
|
100 | if len(vals) > 1: | |||
|
101 | raise KeyError('multiple values for %r' % key) | |||
|
102 | ||||
|
103 | return vals[0] | |||
|
104 | ||||
|
105 | def asdictoflists(self): | |||
|
106 | d = {} | |||
|
107 | for k, v in self._items: | |||
|
108 | if k in d: | |||
|
109 | d[k].append(v) | |||
|
110 | else: | |||
|
111 | d[k] = [v] | |||
|
112 | ||||
|
113 | return d | |||
|
114 | ||||
31 | @attr.s(frozen=True) |
|
115 | @attr.s(frozen=True) | |
32 | class parsedrequest(object): |
|
116 | class parsedrequest(object): | |
33 | """Represents a parsed WSGI request. |
|
117 | """Represents a parsed WSGI request. | |
@@ -56,10 +140,8 b' class parsedrequest(object):' | |||||
56 | havepathinfo = attr.ib() |
|
140 | havepathinfo = attr.ib() | |
57 | # Raw query string (part after "?" in URL). |
|
141 | # Raw query string (part after "?" in URL). | |
58 | querystring = attr.ib() |
|
142 | querystring = attr.ib() | |
59 |
# |
|
143 | # multidict of query string parameters. | |
60 |
q |
|
144 | qsparams = attr.ib() | |
61 | # Dict of query string arguments. Values are lists with at least 1 item. |
|
|||
62 | querystringdict = attr.ib() |
|
|||
63 | # wsgiref.headers.Headers instance. Operates like a dict with case |
|
145 | # wsgiref.headers.Headers instance. Operates like a dict with case | |
64 | # insensitive keys. |
|
146 | # insensitive keys. | |
65 | headers = attr.ib() |
|
147 | headers = attr.ib() | |
@@ -157,14 +239,9 b' def parserequestfromenv(env, bodyfh):' | |||||
157 |
|
239 | |||
158 | # We store as a list so we have ordering information. We also store as |
|
240 | # We store as a list so we have ordering information. We also store as | |
159 | # a dict to facilitate fast lookup. |
|
241 | # a dict to facilitate fast lookup. | |
160 | querystringlist = util.urlreq.parseqsl(querystring, keep_blank_values=True) |
|
242 | qsparams = multidict() | |
161 |
|
243 | for k, v in util.urlreq.parseqsl(querystring, keep_blank_values=True): | ||
162 | querystringdict = {} |
|
244 | qsparams.add(k, v) | |
163 | for k, v in querystringlist: |
|
|||
164 | if k in querystringdict: |
|
|||
165 | querystringdict[k].append(v) |
|
|||
166 | else: |
|
|||
167 | querystringdict[k] = [v] |
|
|||
168 |
|
245 | |||
169 | # HTTP_* keys contain HTTP request headers. The Headers structure should |
|
246 | # HTTP_* keys contain HTTP request headers. The Headers structure should | |
170 | # perform case normalization for us. We just rewrite underscore to dash |
|
247 | # perform case normalization for us. We just rewrite underscore to dash | |
@@ -197,8 +274,7 b' def parserequestfromenv(env, bodyfh):' | |||||
197 | dispatchparts=dispatchparts, dispatchpath=dispatchpath, |
|
274 | dispatchparts=dispatchparts, dispatchpath=dispatchpath, | |
198 | havepathinfo='PATH_INFO' in env, |
|
275 | havepathinfo='PATH_INFO' in env, | |
199 | querystring=querystring, |
|
276 | querystring=querystring, | |
200 |
q |
|
277 | qsparams=qsparams, | |
201 | querystringdict=querystringdict, |
|
|||
202 | headers=headers, |
|
278 | headers=headers, | |
203 | bodyfh=bodyfh) |
|
279 | bodyfh=bodyfh) | |
204 |
|
280 | |||
@@ -350,7 +426,7 b' class wsgirequest(object):' | |||||
350 | self.run_once = wsgienv[r'wsgi.run_once'] |
|
426 | self.run_once = wsgienv[r'wsgi.run_once'] | |
351 | self.env = wsgienv |
|
427 | self.env = wsgienv | |
352 | self.req = parserequestfromenv(wsgienv, inp) |
|
428 | self.req = parserequestfromenv(wsgienv, inp) | |
353 |
self.form = self.req.q |
|
429 | self.form = self.req.qsparams.asdictoflists() | |
354 | self.res = wsgiresponse(self.req, start_response) |
|
430 | self.res = wsgiresponse(self.req, start_response) | |
355 | self._start_response = start_response |
|
431 | self._start_response = start_response | |
356 | self.server_write = None |
|
432 | self.server_write = None |
@@ -79,7 +79,7 b' class httpv1protocolhandler(wireprototyp' | |||||
79 | return [data[k] for k in keys] |
|
79 | return [data[k] for k in keys] | |
80 |
|
80 | |||
81 | def _args(self): |
|
81 | def _args(self): | |
82 | args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy()) |
|
82 | args = self._req.qsparams.asdictoflists() | |
83 | postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0)) |
|
83 | postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0)) | |
84 | if postlen: |
|
84 | if postlen: | |
85 | args.update(urlreq.parseqs( |
|
85 | args.update(urlreq.parseqs( | |
@@ -170,10 +170,10 b' def handlewsgirequest(rctx, wsgireq, req' | |||||
170 | # HTTP version 1 wire protocol requests are denoted by a "cmd" query |
|
170 | # HTTP version 1 wire protocol requests are denoted by a "cmd" query | |
171 | # string parameter. If it isn't present, this isn't a wire protocol |
|
171 | # string parameter. If it isn't present, this isn't a wire protocol | |
172 | # request. |
|
172 | # request. | |
173 |
if 'cmd' not in req.q |
|
173 | if 'cmd' not in req.qsparams: | |
174 | return False |
|
174 | return False | |
175 |
|
175 | |||
176 |
cmd = req.q |
|
176 | cmd = req.qsparams['cmd'] | |
177 |
|
177 | |||
178 | # The "cmd" request parameter is used by both the wire protocol and hgweb. |
|
178 | # The "cmd" request parameter is used by both the wire protocol and hgweb. | |
179 | # While not all wire protocol commands are available for all transports, |
|
179 | # While not all wire protocol commands are available for all transports, |
General Comments 0
You need to be logged in to leave comments.
Login now