Show More
@@ -452,13 +452,10 b' class hgwebdir(object):' | |||
|
452 | 452 | for virtualrepo in _virtualdirs(): |
|
453 | 453 | real = repos.get(virtualrepo) |
|
454 | 454 | if real: |
|
455 | wsgireq.env['REPO_NAME'] = virtualrepo | |
|
456 |
# |
|
|
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. | |
|
455 | # Re-parse the WSGI environment to take into account our | |
|
456 | # repository path component. | |
|
460 | 457 | wsgireq.req = requestmod.parserequestfromenv( |
|
461 | wsgireq.env, wsgireq.req.bodyfh) | |
|
458 | wsgireq.env, wsgireq.req.bodyfh, reponame=virtualrepo) | |
|
462 | 459 | try: |
|
463 | 460 | # ensure caller gets private copy of ui |
|
464 | 461 | repo = hg.repository(self.ui.copy(), real) |
@@ -155,11 +155,16 b' class parsedrequest(object):' | |||
|
155 | 155 | # Request body input stream. |
|
156 | 156 | bodyfh = attr.ib() |
|
157 | 157 | |
|
158 | def parserequestfromenv(env, bodyfh): | |
|
158 | def parserequestfromenv(env, bodyfh, reponame=None): | |
|
159 | 159 | """Parse URL components from environment variables. |
|
160 | 160 | |
|
161 | 161 | WSGI defines request attributes via environment variables. This function |
|
162 | 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 | 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 | 220 | fullurl += '?' + env['QUERY_STRING'] |
|
216 | 221 | advertisedfullurl += '?' + env['QUERY_STRING'] |
|
217 | 222 | |
|
218 | # When dispatching requests, we look at the URL components (PATH_INFO | |
|
219 | # and QUERY_STRING) after the application root (SCRIPT_NAME). But hgwebdir | |
|
220 | # has the concept of "virtual" repositories. This is defined via REPO_NAME. | |
|
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. | |
|
223 | # If ``reponame`` is defined, that must be a prefix on PATH_INFO | |
|
224 | # that represents the repository being dispatched to. When computing | |
|
225 | # the dispatch info, we ignore these leading path components. | |
|
224 | 226 | |
|
225 | 227 | apppath = env.get('SCRIPT_NAME', '') |
|
226 | 228 | |
|
227 | if env.get('REPO_NAME'): | |
|
228 | if not apppath.endswith('/'): | |
|
229 | apppath += '/' | |
|
229 | if reponame: | |
|
230 | repoprefix = '/' + reponame.strip('/') | |
|
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: | |
|
234 | dispatchparts = env['PATH_INFO'].strip('/').split('/') | |
|
240 | dispatchpath = env['PATH_INFO'][len(repoprefix):] | |
|
235 | 241 | |
|
236 | # Strip out repo parts. | |
|
237 | repoparts = env.get('REPO_NAME', '').split('/') | |
|
238 | if dispatchparts[:len(repoparts)] == repoparts: | |
|
239 | dispatchparts = dispatchparts[len(repoparts):] | |
|
242 | if dispatchpath and not dispatchpath.startswith('/'): | |
|
243 | raise error.ProgrammingError('reponame prefix of PATH_INFO does ' | |
|
244 | 'not end at path delimiter: %s (%s)' % | |
|
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 | 251 | else: |
|
241 | 252 | dispatchparts = [] |
|
242 | 253 | |
@@ -283,7 +294,7 b' def parserequestfromenv(env, bodyfh):' | |||
|
283 | 294 | apppath=apppath, |
|
284 | 295 | dispatchparts=dispatchparts, dispatchpath=dispatchpath, |
|
285 | 296 | havepathinfo='PATH_INFO' in env, |
|
286 |
reponame= |
|
|
297 | reponame=reponame, | |
|
287 | 298 | querystring=querystring, |
|
288 | 299 | qsparams=qsparams, |
|
289 | 300 | headers=headers, |
@@ -5,6 +5,9 b' import unittest' | |||
|
5 | 5 | from mercurial.hgweb import ( |
|
6 | 6 | request as requestmod, |
|
7 | 7 | ) |
|
8 | from mercurial import ( | |
|
9 | error, | |
|
10 | ) | |
|
8 | 11 | |
|
9 | 12 | DEFAULT_ENV = { |
|
10 | 13 | r'REQUEST_METHOD': r'GET', |
@@ -20,11 +23,11 b' DEFAULT_ENV = {' | |||
|
20 | 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 | 27 | env = dict(env) |
|
25 | 28 | env.update(extra or {}) |
|
26 | 29 | |
|
27 | return requestmod.parserequestfromenv(env, bodyfh) | |
|
30 | return requestmod.parserequestfromenv(env, bodyfh, reponame=reponame) | |
|
28 | 31 | |
|
29 | 32 | class ParseRequestTests(unittest.TestCase): |
|
30 | 33 | def testdefault(self): |
@@ -203,24 +206,26 b' class ParseRequestTests(unittest.TestCas' | |||
|
203 | 206 | self.assertTrue(r.havepathinfo) |
|
204 | 207 | |
|
205 | 208 | def testreponame(self): |
|
206 |
""" |
|
|
207 | r = parse(DEFAULT_ENV, extra={ | |
|
208 | r'REPO_NAME': r'repo', | |
|
209 | r'PATH_INFO': r'/path1/path2' | |
|
210 | }) | |
|
209 | """repository path components get stripped from URL.""" | |
|
210 | ||
|
211 | with self.assertRaisesRegexp(error.ProgrammingError, | |
|
212 | b'reponame requires PATH_INFO'): | |
|
213 | parse(DEFAULT_ENV, reponame=b'repo') | |
|
211 | 214 | |
|
212 | self.assertEqual(r.url, b'http://testserver/path1/path2') | |
|
213 | self.assertEqual(r.baseurl, b'http://testserver') | |
|
214 | self.assertEqual(r.advertisedurl, b'http://testserver/path1/path2') | |
|
215 | self.assertEqual(r.advertisedbaseurl, b'http://testserver') | |
|
216 | self.assertEqual(r.apppath, b'/repo') | |
|
217 | self.assertEqual(r.dispatchparts, [b'path1', b'path2']) | |
|
218 | self.assertEqual(r.dispatchpath, b'path1/path2') | |
|
219 | self.assertTrue(r.havepathinfo) | |
|
220 | self.assertEqual(r.reponame, b'repo') | |
|
215 | with self.assertRaisesRegexp(error.ProgrammingError, | |
|
216 | b'PATH_INFO does not begin with repo ' | |
|
217 | b'name'): | |
|
218 | parse(DEFAULT_ENV, reponame=b'repo', extra={ | |
|
219 | r'PATH_INFO': r'/pathinfo', | |
|
220 | }) | |
|
221 | 221 | |
|
222 | r = parse(DEFAULT_ENV, extra={ | |
|
223 | r'REPO_NAME': r'repo', | |
|
222 | with self.assertRaisesRegexp(error.ProgrammingError, | |
|
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 | 229 | r'PATH_INFO': r'/repo/path1/path2', |
|
225 | 230 | }) |
|
226 | 231 | |
@@ -234,8 +239,7 b' class ParseRequestTests(unittest.TestCas' | |||
|
234 | 239 | self.assertTrue(r.havepathinfo) |
|
235 | 240 | self.assertEqual(r.reponame, b'repo') |
|
236 | 241 | |
|
237 | r = parse(DEFAULT_ENV, extra={ | |
|
238 | r'REPO_NAME': r'prefix/repo', | |
|
242 | r = parse(DEFAULT_ENV, reponame=b'prefix/repo', extra={ | |
|
239 | 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