##// 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 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 # We have to re-parse because of updated environment
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 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:
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=env.get('REPO_NAME'),
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 """REPO_NAME path components get stripped from URL."""
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