Show More
@@ -452,13 +452,10 b' class hgwebdir(object):' | |||||
452 | for virtualrepo in _virtualdirs(): |
|
452 | for virtualrepo in _virtualdirs(): | |
453 | real = repos.get(virtualrepo) |
|
453 | real = repos.get(virtualrepo) | |
454 | if real: |
|
454 | if real: | |
455 | wsgireq.env['REPO_NAME'] = virtualrepo |
|
455 | # Re-parse the WSGI environment to take into account our | |
456 |
# |
|
456 | # repository path component. | |
457 | # variable. |
|
|||
458 | # TODO this is kind of hacky and we should have a better |
|
|||
459 | # way of doing this than with REPO_NAME side-effects. |
|
|||
460 | wsgireq.req = requestmod.parserequestfromenv( |
|
457 | wsgireq.req = requestmod.parserequestfromenv( | |
461 | wsgireq.env, wsgireq.req.bodyfh) |
|
458 | wsgireq.env, wsgireq.req.bodyfh, reponame=virtualrepo) | |
462 | try: |
|
459 | try: | |
463 | # ensure caller gets private copy of ui |
|
460 | # ensure caller gets private copy of ui | |
464 | repo = hg.repository(self.ui.copy(), real) |
|
461 | repo = hg.repository(self.ui.copy(), real) |
@@ -155,11 +155,16 b' class parsedrequest(object):' | |||||
155 | # Request body input stream. |
|
155 | # Request body input stream. | |
156 | bodyfh = attr.ib() |
|
156 | bodyfh = attr.ib() | |
157 |
|
157 | |||
158 | def parserequestfromenv(env, bodyfh): |
|
158 | def parserequestfromenv(env, bodyfh, reponame=None): | |
159 | """Parse URL components from environment variables. |
|
159 | """Parse URL components from environment variables. | |
160 |
|
160 | |||
161 | WSGI defines request attributes via environment variables. This function |
|
161 | WSGI defines request attributes via environment variables. This function | |
162 | parses the environment variables into a data structure. |
|
162 | parses the environment variables into a data structure. | |
|
163 | ||||
|
164 | If ``reponame`` is defined, the leading path components matching that | |||
|
165 | string are effectively shifted from ``PATH_INFO`` to ``SCRIPT_NAME``. | |||
|
166 | This simulates the world view of a WSGI application that processes | |||
|
167 | requests from the base URL of a repo. | |||
163 | """ |
|
168 | """ | |
164 | # PEP-0333 defines the WSGI spec and is a useful reference for this code. |
|
169 | # PEP-0333 defines the WSGI spec and is a useful reference for this code. | |
165 |
|
170 | |||
@@ -215,28 +220,34 b' def parserequestfromenv(env, bodyfh):' | |||||
215 | fullurl += '?' + env['QUERY_STRING'] |
|
220 | fullurl += '?' + env['QUERY_STRING'] | |
216 | advertisedfullurl += '?' + env['QUERY_STRING'] |
|
221 | advertisedfullurl += '?' + env['QUERY_STRING'] | |
217 |
|
222 | |||
218 | # When dispatching requests, we look at the URL components (PATH_INFO |
|
223 | # If ``reponame`` is defined, that must be a prefix on PATH_INFO | |
219 | # and QUERY_STRING) after the application root (SCRIPT_NAME). But hgwebdir |
|
224 | # that represents the repository being dispatched to. When computing | |
220 | # has the concept of "virtual" repositories. This is defined via REPO_NAME. |
|
225 | # the dispatch info, we ignore these leading path components. | |
221 | # If REPO_NAME is defined, we append it to SCRIPT_NAME to form a new app |
|
|||
222 | # root. We also exclude its path components from PATH_INFO when resolving |
|
|||
223 | # the dispatch path. |
|
|||
224 |
|
226 | |||
225 | apppath = env.get('SCRIPT_NAME', '') |
|
227 | apppath = env.get('SCRIPT_NAME', '') | |
226 |
|
228 | |||
227 | if env.get('REPO_NAME'): |
|
229 | if reponame: | |
228 | if not apppath.endswith('/'): |
|
230 | repoprefix = '/' + reponame.strip('/') | |
229 | apppath += '/' |
|
|||
230 |
|
231 | |||
231 |
|
|
232 | if not env.get('PATH_INFO'): | |
|
233 | raise error.ProgrammingError('reponame requires PATH_INFO') | |||
|
234 | ||||
|
235 | if not env['PATH_INFO'].startswith(repoprefix): | |||
|
236 | raise error.ProgrammingError('PATH_INFO does not begin with repo ' | |||
|
237 | 'name: %s (%s)' % (env['PATH_INFO'], | |||
|
238 | reponame)) | |||
232 |
|
239 | |||
233 | if 'PATH_INFO' in env: |
|
240 | dispatchpath = env['PATH_INFO'][len(repoprefix):] | |
234 | dispatchparts = env['PATH_INFO'].strip('/').split('/') |
|
|||
235 |
|
241 | |||
236 | # Strip out repo parts. |
|
242 | if dispatchpath and not dispatchpath.startswith('/'): | |
237 | repoparts = env.get('REPO_NAME', '').split('/') |
|
243 | raise error.ProgrammingError('reponame prefix of PATH_INFO does ' | |
238 | if dispatchparts[:len(repoparts)] == repoparts: |
|
244 | 'not end at path delimiter: %s (%s)' % | |
239 | dispatchparts = dispatchparts[len(repoparts):] |
|
245 | (env['PATH_INFO'], reponame)) | |
|
246 | ||||
|
247 | apppath = apppath.rstrip('/') + repoprefix | |||
|
248 | dispatchparts = dispatchpath.strip('/').split('/') | |||
|
249 | elif env.get('PATH_INFO', '').strip('/'): | |||
|
250 | dispatchparts = env['PATH_INFO'].strip('/').split('/') | |||
240 | else: |
|
251 | else: | |
241 | dispatchparts = [] |
|
252 | dispatchparts = [] | |
242 |
|
253 | |||
@@ -283,7 +294,7 b' def parserequestfromenv(env, bodyfh):' | |||||
283 | apppath=apppath, |
|
294 | apppath=apppath, | |
284 | dispatchparts=dispatchparts, dispatchpath=dispatchpath, |
|
295 | dispatchparts=dispatchparts, dispatchpath=dispatchpath, | |
285 | havepathinfo='PATH_INFO' in env, |
|
296 | havepathinfo='PATH_INFO' in env, | |
286 |
reponame= |
|
297 | reponame=reponame, | |
287 | querystring=querystring, |
|
298 | querystring=querystring, | |
288 | qsparams=qsparams, |
|
299 | qsparams=qsparams, | |
289 | headers=headers, |
|
300 | headers=headers, |
@@ -5,6 +5,9 b' import unittest' | |||||
5 | from mercurial.hgweb import ( |
|
5 | from mercurial.hgweb import ( | |
6 | request as requestmod, |
|
6 | request as requestmod, | |
7 | ) |
|
7 | ) | |
|
8 | from mercurial import ( | |||
|
9 | error, | |||
|
10 | ) | |||
8 |
|
11 | |||
9 | DEFAULT_ENV = { |
|
12 | DEFAULT_ENV = { | |
10 | r'REQUEST_METHOD': r'GET', |
|
13 | r'REQUEST_METHOD': r'GET', | |
@@ -20,11 +23,11 b' DEFAULT_ENV = {' | |||||
20 | r'wsgi.run_once': False, |
|
23 | r'wsgi.run_once': False, | |
21 | } |
|
24 | } | |
22 |
|
25 | |||
23 | def parse(env, bodyfh=None, extra=None): |
|
26 | def parse(env, bodyfh=None, reponame=None, extra=None): | |
24 | env = dict(env) |
|
27 | env = dict(env) | |
25 | env.update(extra or {}) |
|
28 | env.update(extra or {}) | |
26 |
|
29 | |||
27 | return requestmod.parserequestfromenv(env, bodyfh) |
|
30 | return requestmod.parserequestfromenv(env, bodyfh, reponame=reponame) | |
28 |
|
31 | |||
29 | class ParseRequestTests(unittest.TestCase): |
|
32 | class ParseRequestTests(unittest.TestCase): | |
30 | def testdefault(self): |
|
33 | def testdefault(self): | |
@@ -203,24 +206,26 b' class ParseRequestTests(unittest.TestCas' | |||||
203 | self.assertTrue(r.havepathinfo) |
|
206 | self.assertTrue(r.havepathinfo) | |
204 |
|
207 | |||
205 | def testreponame(self): |
|
208 | def testreponame(self): | |
206 |
""" |
|
209 | """repository path components get stripped from URL.""" | |
207 | r = parse(DEFAULT_ENV, extra={ |
|
210 | ||
208 | r'REPO_NAME': r'repo', |
|
211 | with self.assertRaisesRegexp(error.ProgrammingError, | |
209 | r'PATH_INFO': r'/path1/path2' |
|
212 | b'reponame requires PATH_INFO'): | |
210 | }) |
|
213 | parse(DEFAULT_ENV, reponame=b'repo') | |
211 |
|
214 | |||
212 | self.assertEqual(r.url, b'http://testserver/path1/path2') |
|
215 | with self.assertRaisesRegexp(error.ProgrammingError, | |
213 | self.assertEqual(r.baseurl, b'http://testserver') |
|
216 | b'PATH_INFO does not begin with repo ' | |
214 | self.assertEqual(r.advertisedurl, b'http://testserver/path1/path2') |
|
217 | b'name'): | |
215 | self.assertEqual(r.advertisedbaseurl, b'http://testserver') |
|
218 | parse(DEFAULT_ENV, reponame=b'repo', extra={ | |
216 | self.assertEqual(r.apppath, b'/repo') |
|
219 | r'PATH_INFO': r'/pathinfo', | |
217 | self.assertEqual(r.dispatchparts, [b'path1', b'path2']) |
|
220 | }) | |
218 | self.assertEqual(r.dispatchpath, b'path1/path2') |
|
|||
219 | self.assertTrue(r.havepathinfo) |
|
|||
220 | self.assertEqual(r.reponame, b'repo') |
|
|||
221 |
|
221 | |||
222 | r = parse(DEFAULT_ENV, extra={ |
|
222 | with self.assertRaisesRegexp(error.ProgrammingError, | |
223 | r'REPO_NAME': r'repo', |
|
223 | b'reponame prefix of PATH_INFO'): | |
|
224 | parse(DEFAULT_ENV, reponame=b'repo', extra={ | |||
|
225 | r'PATH_INFO': r'/repoextra/path', | |||
|
226 | }) | |||
|
227 | ||||
|
228 | r = parse(DEFAULT_ENV, reponame=b'repo', extra={ | |||
224 | r'PATH_INFO': r'/repo/path1/path2', |
|
229 | r'PATH_INFO': r'/repo/path1/path2', | |
225 | }) |
|
230 | }) | |
226 |
|
231 | |||
@@ -234,8 +239,7 b' class ParseRequestTests(unittest.TestCas' | |||||
234 | self.assertTrue(r.havepathinfo) |
|
239 | self.assertTrue(r.havepathinfo) | |
235 | self.assertEqual(r.reponame, b'repo') |
|
240 | self.assertEqual(r.reponame, b'repo') | |
236 |
|
241 | |||
237 | r = parse(DEFAULT_ENV, extra={ |
|
242 | r = parse(DEFAULT_ENV, reponame=b'prefix/repo', extra={ | |
238 | r'REPO_NAME': r'prefix/repo', |
|
|||
239 | r'PATH_INFO': r'/prefix/repo/path1/path2', |
|
243 | r'PATH_INFO': r'/prefix/repo/path1/path2', | |
240 | }) |
|
244 | }) | |
241 |
|
245 |
General Comments 0
You need to be logged in to leave comments.
Login now