##// END OF EJS Templates
hgweb: refactor repository name URL parsing...
Gregory Szorc -
r36913:d7fd203e default
parent child Browse files
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 # We have to re-parse because of updated environment
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 apppath += env.get('REPO_NAME')
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=env.get('REPO_NAME'),
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 """REPO_NAME path components get stripped from URL."""
207 r = parse(DEFAULT_ENV, extra={
208 r'REPO_NAME': r'repo',
209 r'PATH_INFO': r'/path1/path2'
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')
214
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',
210 220 })
211 221
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')
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 })
221 227
222 r = parse(DEFAULT_ENV, extra={
223 r'REPO_NAME': r'repo',
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