##// END OF EJS Templates
hgweb: extract _getview to own function...
Gregory Szorc -
r26208:c87566ac default
parent child Browse files
Show More
@@ -1,446 +1,447
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import os
9 import os
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 from mercurial.templatefilters import websub
11 from mercurial.templatefilters import websub
12 from common import get_stat, ErrorResponse, permhooks, caching
12 from common import get_stat, ErrorResponse, permhooks, caching
13 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
13 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
14 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from request import wsgirequest
15 from request import wsgirequest
16 import webcommands, protocol, webutil
16 import webcommands, protocol, webutil
17
17
18 perms = {
18 perms = {
19 'changegroup': 'pull',
19 'changegroup': 'pull',
20 'changegroupsubset': 'pull',
20 'changegroupsubset': 'pull',
21 'getbundle': 'pull',
21 'getbundle': 'pull',
22 'stream_out': 'pull',
22 'stream_out': 'pull',
23 'listkeys': 'pull',
23 'listkeys': 'pull',
24 'unbundle': 'push',
24 'unbundle': 'push',
25 'pushkey': 'push',
25 'pushkey': 'push',
26 }
26 }
27
27
28 ## Files of interest
28 ## Files of interest
29 # Used to check if the repository has changed looking at mtime and size of
29 # Used to check if the repository has changed looking at mtime and size of
30 # theses files. This should probably be relocated a bit higher in core.
30 # theses files. This should probably be relocated a bit higher in core.
31 foi = [('spath', '00changelog.i'),
31 foi = [('spath', '00changelog.i'),
32 ('spath', 'phaseroots'), # ! phase can change content at the same size
32 ('spath', 'phaseroots'), # ! phase can change content at the same size
33 ('spath', 'obsstore'),
33 ('spath', 'obsstore'),
34 ('path', 'bookmarks'), # ! bookmark can change content at the same size
34 ('path', 'bookmarks'), # ! bookmark can change content at the same size
35 ]
35 ]
36
36
37 def makebreadcrumb(url, prefix=''):
37 def makebreadcrumb(url, prefix=''):
38 '''Return a 'URL breadcrumb' list
38 '''Return a 'URL breadcrumb' list
39
39
40 A 'URL breadcrumb' is a list of URL-name pairs,
40 A 'URL breadcrumb' is a list of URL-name pairs,
41 corresponding to each of the path items on a URL.
41 corresponding to each of the path items on a URL.
42 This can be used to create path navigation entries.
42 This can be used to create path navigation entries.
43 '''
43 '''
44 if url.endswith('/'):
44 if url.endswith('/'):
45 url = url[:-1]
45 url = url[:-1]
46 if prefix:
46 if prefix:
47 url = '/' + prefix + url
47 url = '/' + prefix + url
48 relpath = url
48 relpath = url
49 if relpath.startswith('/'):
49 if relpath.startswith('/'):
50 relpath = relpath[1:]
50 relpath = relpath[1:]
51
51
52 breadcrumb = []
52 breadcrumb = []
53 urlel = url
53 urlel = url
54 pathitems = [''] + relpath.split('/')
54 pathitems = [''] + relpath.split('/')
55 for pathel in reversed(pathitems):
55 for pathel in reversed(pathitems):
56 if not pathel or not urlel:
56 if not pathel or not urlel:
57 break
57 break
58 breadcrumb.append({'url': urlel, 'name': pathel})
58 breadcrumb.append({'url': urlel, 'name': pathel})
59 urlel = os.path.dirname(urlel)
59 urlel = os.path.dirname(urlel)
60 return reversed(breadcrumb)
60 return reversed(breadcrumb)
61
61
62 class requestcontext(object):
62 class requestcontext(object):
63 """Holds state/context for an individual request.
63 """Holds state/context for an individual request.
64
64
65 Servers can be multi-threaded. Holding state on the WSGI application
65 Servers can be multi-threaded. Holding state on the WSGI application
66 is prone to race conditions. Instances of this class exist to hold
66 is prone to race conditions. Instances of this class exist to hold
67 mutable and race-free state for requests.
67 mutable and race-free state for requests.
68 """
68 """
69 def __init__(self, app):
69 def __init__(self, app):
70 object.__setattr__(self, 'app', app)
70 object.__setattr__(self, 'app', app)
71 object.__setattr__(self, 'repo', app.repo)
71 object.__setattr__(self, 'repo', app.repo)
72
72
73 object.__setattr__(self, 'archives', ('zip', 'gz', 'bz2'))
73 object.__setattr__(self, 'archives', ('zip', 'gz', 'bz2'))
74
74
75 object.__setattr__(self, 'maxchanges',
75 object.__setattr__(self, 'maxchanges',
76 self.configint('web', 'maxchanges', 10))
76 self.configint('web', 'maxchanges', 10))
77 object.__setattr__(self, 'stripecount',
77 object.__setattr__(self, 'stripecount',
78 self.configint('web', 'stripes', 1))
78 self.configint('web', 'stripes', 1))
79 object.__setattr__(self, 'maxshortchanges',
79 object.__setattr__(self, 'maxshortchanges',
80 self.configint('web', 'maxshortchanges', 60))
80 self.configint('web', 'maxshortchanges', 60))
81 object.__setattr__(self, 'maxfiles',
81 object.__setattr__(self, 'maxfiles',
82 self.configint('web', 'maxfiles', 10))
82 self.configint('web', 'maxfiles', 10))
83 object.__setattr__(self, 'allowpull',
83 object.__setattr__(self, 'allowpull',
84 self.configbool('web', 'allowpull', True))
84 self.configbool('web', 'allowpull', True))
85
85
86 # we use untrusted=False to prevent a repo owner from using
86 # we use untrusted=False to prevent a repo owner from using
87 # web.templates in .hg/hgrc to get access to any file readable
87 # web.templates in .hg/hgrc to get access to any file readable
88 # by the user running the CGI script
88 # by the user running the CGI script
89 object.__setattr__(self, 'templatepath',
89 object.__setattr__(self, 'templatepath',
90 self.config('web', 'templates', untrusted=False))
90 self.config('web', 'templates', untrusted=False))
91
91
92 # This object is more expensive to build than simple config values.
92 # This object is more expensive to build than simple config values.
93 # It is shared across requests. The app will replace the object
93 # It is shared across requests. The app will replace the object
94 # if it is updated. Since this is a reference and nothing should
94 # if it is updated. Since this is a reference and nothing should
95 # modify the underlying object, it should be constant for the lifetime
95 # modify the underlying object, it should be constant for the lifetime
96 # of the request.
96 # of the request.
97 object.__setattr__(self, 'websubtable', app.websubtable)
97 object.__setattr__(self, 'websubtable', app.websubtable)
98
98
99 # Proxy unknown reads and writes to the application instance
99 # Proxy unknown reads and writes to the application instance
100 # until everything is moved to us.
100 # until everything is moved to us.
101 def __getattr__(self, name):
101 def __getattr__(self, name):
102 return getattr(self.app, name)
102 return getattr(self.app, name)
103
103
104 def __setattr__(self, name, value):
104 def __setattr__(self, name, value):
105 return setattr(self.app, name, value)
105 return setattr(self.app, name, value)
106
106
107 # Servers are often run by a user different from the repo owner.
107 # Servers are often run by a user different from the repo owner.
108 # Trust the settings from the .hg/hgrc files by default.
108 # Trust the settings from the .hg/hgrc files by default.
109 def config(self, section, name, default=None, untrusted=True):
109 def config(self, section, name, default=None, untrusted=True):
110 return self.repo.ui.config(section, name, default,
110 return self.repo.ui.config(section, name, default,
111 untrusted=untrusted)
111 untrusted=untrusted)
112
112
113 def configbool(self, section, name, default=False, untrusted=True):
113 def configbool(self, section, name, default=False, untrusted=True):
114 return self.repo.ui.configbool(section, name, default,
114 return self.repo.ui.configbool(section, name, default,
115 untrusted=untrusted)
115 untrusted=untrusted)
116
116
117 def configint(self, section, name, default=None, untrusted=True):
117 def configint(self, section, name, default=None, untrusted=True):
118 return self.repo.ui.configint(section, name, default,
118 return self.repo.ui.configint(section, name, default,
119 untrusted=untrusted)
119 untrusted=untrusted)
120
120
121 def configlist(self, section, name, default=None, untrusted=True):
121 def configlist(self, section, name, default=None, untrusted=True):
122 return self.repo.ui.configlist(section, name, default,
122 return self.repo.ui.configlist(section, name, default,
123 untrusted=untrusted)
123 untrusted=untrusted)
124
124
125 archivespecs = {
125 archivespecs = {
126 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
126 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
127 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
127 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
128 'zip': ('application/zip', 'zip', '.zip', None),
128 'zip': ('application/zip', 'zip', '.zip', None),
129 }
129 }
130
130
131 def archivelist(self, nodeid):
131 def archivelist(self, nodeid):
132 allowed = self.configlist('web', 'allow_archive')
132 allowed = self.configlist('web', 'allow_archive')
133 for typ, spec in self.archivespecs.iteritems():
133 for typ, spec in self.archivespecs.iteritems():
134 if typ in allowed or self.configbool('web', 'allow%s' % typ):
134 if typ in allowed or self.configbool('web', 'allow%s' % typ):
135 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
135 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
136
136
137 def templater(self, req):
137 def templater(self, req):
138 # determine scheme, port and server name
138 # determine scheme, port and server name
139 # this is needed to create absolute urls
139 # this is needed to create absolute urls
140
140
141 proto = req.env.get('wsgi.url_scheme')
141 proto = req.env.get('wsgi.url_scheme')
142 if proto == 'https':
142 if proto == 'https':
143 proto = 'https'
143 proto = 'https'
144 default_port = '443'
144 default_port = '443'
145 else:
145 else:
146 proto = 'http'
146 proto = 'http'
147 default_port = '80'
147 default_port = '80'
148
148
149 port = req.env['SERVER_PORT']
149 port = req.env['SERVER_PORT']
150 port = port != default_port and (':' + port) or ''
150 port = port != default_port and (':' + port) or ''
151 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
151 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
152 logourl = self.config('web', 'logourl', 'http://mercurial.selenic.com/')
152 logourl = self.config('web', 'logourl', 'http://mercurial.selenic.com/')
153 logoimg = self.config('web', 'logoimg', 'hglogo.png')
153 logoimg = self.config('web', 'logoimg', 'hglogo.png')
154 staticurl = self.config('web', 'staticurl') or req.url + 'static/'
154 staticurl = self.config('web', 'staticurl') or req.url + 'static/'
155 if not staticurl.endswith('/'):
155 if not staticurl.endswith('/'):
156 staticurl += '/'
156 staticurl += '/'
157
157
158 # some functions for the templater
158 # some functions for the templater
159
159
160 def motd(**map):
160 def motd(**map):
161 yield self.config('web', 'motd', '')
161 yield self.config('web', 'motd', '')
162
162
163 # figure out which style to use
163 # figure out which style to use
164
164
165 vars = {}
165 vars = {}
166 styles = (
166 styles = (
167 req.form.get('style', [None])[0],
167 req.form.get('style', [None])[0],
168 self.config('web', 'style'),
168 self.config('web', 'style'),
169 'paper',
169 'paper',
170 )
170 )
171 style, mapfile = templater.stylemap(styles, self.templatepath)
171 style, mapfile = templater.stylemap(styles, self.templatepath)
172 if style == styles[0]:
172 if style == styles[0]:
173 vars['style'] = style
173 vars['style'] = style
174
174
175 start = req.url[-1] == '?' and '&' or '?'
175 start = req.url[-1] == '?' and '&' or '?'
176 sessionvars = webutil.sessionvars(vars, start)
176 sessionvars = webutil.sessionvars(vars, start)
177
177
178 if not self.reponame:
178 if not self.reponame:
179 self.reponame = (self.config('web', 'name')
179 self.reponame = (self.config('web', 'name')
180 or req.env.get('REPO_NAME')
180 or req.env.get('REPO_NAME')
181 or req.url.strip('/') or self.repo.root)
181 or req.url.strip('/') or self.repo.root)
182
182
183 def websubfilter(text):
183 def websubfilter(text):
184 return websub(text, self.websubtable)
184 return websub(text, self.websubtable)
185
185
186 # create the templater
186 # create the templater
187
187
188 tmpl = templater.templater(mapfile,
188 tmpl = templater.templater(mapfile,
189 filters={'websub': websubfilter},
189 filters={'websub': websubfilter},
190 defaults={'url': req.url,
190 defaults={'url': req.url,
191 'logourl': logourl,
191 'logourl': logourl,
192 'logoimg': logoimg,
192 'logoimg': logoimg,
193 'staticurl': staticurl,
193 'staticurl': staticurl,
194 'urlbase': urlbase,
194 'urlbase': urlbase,
195 'repo': self.reponame,
195 'repo': self.reponame,
196 'encoding': encoding.encoding,
196 'encoding': encoding.encoding,
197 'motd': motd,
197 'motd': motd,
198 'sessionvars': sessionvars,
198 'sessionvars': sessionvars,
199 'pathdef': makebreadcrumb(req.url),
199 'pathdef': makebreadcrumb(req.url),
200 'style': style,
200 'style': style,
201 })
201 })
202 return tmpl
202 return tmpl
203
203
204
204
205 class hgweb(object):
205 class hgweb(object):
206 """HTTP server for individual repositories.
206 """HTTP server for individual repositories.
207
207
208 Instances of this class serve HTTP responses for a particular
208 Instances of this class serve HTTP responses for a particular
209 repository.
209 repository.
210
210
211 Instances are typically used as WSGI applications.
211 Instances are typically used as WSGI applications.
212
212
213 Some servers are multi-threaded. On these servers, there may
213 Some servers are multi-threaded. On these servers, there may
214 be multiple active threads inside __call__.
214 be multiple active threads inside __call__.
215 """
215 """
216 def __init__(self, repo, name=None, baseui=None):
216 def __init__(self, repo, name=None, baseui=None):
217 if isinstance(repo, str):
217 if isinstance(repo, str):
218 if baseui:
218 if baseui:
219 u = baseui.copy()
219 u = baseui.copy()
220 else:
220 else:
221 u = ui.ui()
221 u = ui.ui()
222 r = hg.repository(u, repo)
222 r = hg.repository(u, repo)
223 else:
223 else:
224 # we trust caller to give us a private copy
224 # we trust caller to give us a private copy
225 r = repo
225 r = repo
226
226
227 r = self._getview(r)
227 r = getwebview(r)
228 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
228 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
229 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
229 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
230 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
230 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
231 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
231 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
232 # displaying bundling progress bar while serving feel wrong and may
232 # displaying bundling progress bar while serving feel wrong and may
233 # break some wsgi implementation.
233 # break some wsgi implementation.
234 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
234 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
235 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
235 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
236 self.repo = r
236 self.repo = r
237 hook.redirect(True)
237 hook.redirect(True)
238 self.repostate = None
238 self.repostate = None
239 self.mtime = -1
239 self.mtime = -1
240 self.reponame = name
240 self.reponame = name
241
241
242 def _getview(self, repo):
243 """The 'web.view' config controls changeset filter to hgweb. Possible
244 values are ``served``, ``visible`` and ``all``. Default is ``served``.
245 The ``served`` filter only shows changesets that can be pulled from the
246 hgweb instance. The``visible`` filter includes secret changesets but
247 still excludes "hidden" one.
248
249 See the repoview module for details.
250
251 The option has been around undocumented since Mercurial 2.5, but no
252 user ever asked about it. So we better keep it undocumented for now."""
253 viewconfig = repo.ui.config('web', 'view', 'served',
254 untrusted=True)
255 if viewconfig == 'all':
256 return repo.unfiltered()
257 elif viewconfig in repoview.filtertable:
258 return repo.filtered(viewconfig)
259 else:
260 return repo.filtered('served')
261
262 def refresh(self):
242 def refresh(self):
263 repostate = []
243 repostate = []
264 mtime = 0
244 mtime = 0
265 # file of interrests mtime and size
245 # file of interrests mtime and size
266 for meth, fname in foi:
246 for meth, fname in foi:
267 prefix = getattr(self.repo, meth)
247 prefix = getattr(self.repo, meth)
268 st = get_stat(prefix, fname)
248 st = get_stat(prefix, fname)
269 repostate.append((st.st_mtime, st.st_size))
249 repostate.append((st.st_mtime, st.st_size))
270 mtime = max(mtime, st.st_mtime)
250 mtime = max(mtime, st.st_mtime)
271 repostate = tuple(repostate)
251 repostate = tuple(repostate)
272 # we need to compare file size in addition to mtime to catch
252 # we need to compare file size in addition to mtime to catch
273 # changes made less than a second ago
253 # changes made less than a second ago
274 if repostate != self.repostate:
254 if repostate != self.repostate:
275 r = hg.repository(self.repo.baseui, self.repo.url())
255 r = hg.repository(self.repo.baseui, self.repo.url())
276 self.repo = self._getview(r)
256 self.repo = getwebview(r)
277 # update these last to avoid threads seeing empty settings
257 # update these last to avoid threads seeing empty settings
278 self.repostate = repostate
258 self.repostate = repostate
279 # mtime is needed for ETag
259 # mtime is needed for ETag
280 self.mtime = mtime
260 self.mtime = mtime
281
261
282 self.websubtable = webutil.getwebsubs(r)
262 self.websubtable = webutil.getwebsubs(r)
283
263
284 def run(self):
264 def run(self):
285 """Start a server from CGI environment.
265 """Start a server from CGI environment.
286
266
287 Modern servers should be using WSGI and should avoid this
267 Modern servers should be using WSGI and should avoid this
288 method, if possible.
268 method, if possible.
289 """
269 """
290 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
270 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
291 raise RuntimeError("This function is only intended to be "
271 raise RuntimeError("This function is only intended to be "
292 "called while running as a CGI script.")
272 "called while running as a CGI script.")
293 import mercurial.hgweb.wsgicgi as wsgicgi
273 import mercurial.hgweb.wsgicgi as wsgicgi
294 wsgicgi.launch(self)
274 wsgicgi.launch(self)
295
275
296 def __call__(self, env, respond):
276 def __call__(self, env, respond):
297 """Run the WSGI application.
277 """Run the WSGI application.
298
278
299 This may be called by multiple threads.
279 This may be called by multiple threads.
300 """
280 """
301 req = wsgirequest(env, respond)
281 req = wsgirequest(env, respond)
302 return self.run_wsgi(req)
282 return self.run_wsgi(req)
303
283
304 def run_wsgi(self, req):
284 def run_wsgi(self, req):
305 """Internal method to run the WSGI application.
285 """Internal method to run the WSGI application.
306
286
307 This is typically only called by Mercurial. External consumers
287 This is typically only called by Mercurial. External consumers
308 should be using instances of this class as the WSGI application.
288 should be using instances of this class as the WSGI application.
309 """
289 """
310 self.refresh()
290 self.refresh()
311 rctx = requestcontext(self)
291 rctx = requestcontext(self)
312
292
313 # This state is global across all threads.
293 # This state is global across all threads.
314 encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
294 encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
315 rctx.repo.ui.environ = req.env
295 rctx.repo.ui.environ = req.env
316
296
317 # work with CGI variables to create coherent structure
297 # work with CGI variables to create coherent structure
318 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
298 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
319
299
320 req.url = req.env['SCRIPT_NAME']
300 req.url = req.env['SCRIPT_NAME']
321 if not req.url.endswith('/'):
301 if not req.url.endswith('/'):
322 req.url += '/'
302 req.url += '/'
323 if 'REPO_NAME' in req.env:
303 if 'REPO_NAME' in req.env:
324 req.url += req.env['REPO_NAME'] + '/'
304 req.url += req.env['REPO_NAME'] + '/'
325
305
326 if 'PATH_INFO' in req.env:
306 if 'PATH_INFO' in req.env:
327 parts = req.env['PATH_INFO'].strip('/').split('/')
307 parts = req.env['PATH_INFO'].strip('/').split('/')
328 repo_parts = req.env.get('REPO_NAME', '').split('/')
308 repo_parts = req.env.get('REPO_NAME', '').split('/')
329 if parts[:len(repo_parts)] == repo_parts:
309 if parts[:len(repo_parts)] == repo_parts:
330 parts = parts[len(repo_parts):]
310 parts = parts[len(repo_parts):]
331 query = '/'.join(parts)
311 query = '/'.join(parts)
332 else:
312 else:
333 query = req.env['QUERY_STRING'].split('&', 1)[0]
313 query = req.env['QUERY_STRING'].split('&', 1)[0]
334 query = query.split(';', 1)[0]
314 query = query.split(';', 1)[0]
335
315
336 # process this if it's a protocol request
316 # process this if it's a protocol request
337 # protocol bits don't need to create any URLs
317 # protocol bits don't need to create any URLs
338 # and the clients always use the old URL structure
318 # and the clients always use the old URL structure
339
319
340 cmd = req.form.get('cmd', [''])[0]
320 cmd = req.form.get('cmd', [''])[0]
341 if protocol.iscmd(cmd):
321 if protocol.iscmd(cmd):
342 try:
322 try:
343 if query:
323 if query:
344 raise ErrorResponse(HTTP_NOT_FOUND)
324 raise ErrorResponse(HTTP_NOT_FOUND)
345 if cmd in perms:
325 if cmd in perms:
346 self.check_perm(rctx, req, perms[cmd])
326 self.check_perm(rctx, req, perms[cmd])
347 return protocol.call(self.repo, req, cmd)
327 return protocol.call(self.repo, req, cmd)
348 except ErrorResponse as inst:
328 except ErrorResponse as inst:
349 # A client that sends unbundle without 100-continue will
329 # A client that sends unbundle without 100-continue will
350 # break if we respond early.
330 # break if we respond early.
351 if (cmd == 'unbundle' and
331 if (cmd == 'unbundle' and
352 (req.env.get('HTTP_EXPECT',
332 (req.env.get('HTTP_EXPECT',
353 '').lower() != '100-continue') or
333 '').lower() != '100-continue') or
354 req.env.get('X-HgHttp2', '')):
334 req.env.get('X-HgHttp2', '')):
355 req.drain()
335 req.drain()
356 else:
336 else:
357 req.headers.append(('Connection', 'Close'))
337 req.headers.append(('Connection', 'Close'))
358 req.respond(inst, protocol.HGTYPE,
338 req.respond(inst, protocol.HGTYPE,
359 body='0\n%s\n' % inst)
339 body='0\n%s\n' % inst)
360 return ''
340 return ''
361
341
362 # translate user-visible url structure to internal structure
342 # translate user-visible url structure to internal structure
363
343
364 args = query.split('/', 2)
344 args = query.split('/', 2)
365 if 'cmd' not in req.form and args and args[0]:
345 if 'cmd' not in req.form and args and args[0]:
366
346
367 cmd = args.pop(0)
347 cmd = args.pop(0)
368 style = cmd.rfind('-')
348 style = cmd.rfind('-')
369 if style != -1:
349 if style != -1:
370 req.form['style'] = [cmd[:style]]
350 req.form['style'] = [cmd[:style]]
371 cmd = cmd[style + 1:]
351 cmd = cmd[style + 1:]
372
352
373 # avoid accepting e.g. style parameter as command
353 # avoid accepting e.g. style parameter as command
374 if util.safehasattr(webcommands, cmd):
354 if util.safehasattr(webcommands, cmd):
375 req.form['cmd'] = [cmd]
355 req.form['cmd'] = [cmd]
376
356
377 if cmd == 'static':
357 if cmd == 'static':
378 req.form['file'] = ['/'.join(args)]
358 req.form['file'] = ['/'.join(args)]
379 else:
359 else:
380 if args and args[0]:
360 if args and args[0]:
381 node = args.pop(0).replace('%2F', '/')
361 node = args.pop(0).replace('%2F', '/')
382 req.form['node'] = [node]
362 req.form['node'] = [node]
383 if args:
363 if args:
384 req.form['file'] = args
364 req.form['file'] = args
385
365
386 ua = req.env.get('HTTP_USER_AGENT', '')
366 ua = req.env.get('HTTP_USER_AGENT', '')
387 if cmd == 'rev' and 'mercurial' in ua:
367 if cmd == 'rev' and 'mercurial' in ua:
388 req.form['style'] = ['raw']
368 req.form['style'] = ['raw']
389
369
390 if cmd == 'archive':
370 if cmd == 'archive':
391 fn = req.form['node'][0]
371 fn = req.form['node'][0]
392 for type_, spec in rctx.archivespecs.iteritems():
372 for type_, spec in rctx.archivespecs.iteritems():
393 ext = spec[2]
373 ext = spec[2]
394 if fn.endswith(ext):
374 if fn.endswith(ext):
395 req.form['node'] = [fn[:-len(ext)]]
375 req.form['node'] = [fn[:-len(ext)]]
396 req.form['type'] = [type_]
376 req.form['type'] = [type_]
397
377
398 # process the web interface request
378 # process the web interface request
399
379
400 try:
380 try:
401 tmpl = rctx.templater(req)
381 tmpl = rctx.templater(req)
402 ctype = tmpl('mimetype', encoding=encoding.encoding)
382 ctype = tmpl('mimetype', encoding=encoding.encoding)
403 ctype = templater.stringify(ctype)
383 ctype = templater.stringify(ctype)
404
384
405 # check read permissions non-static content
385 # check read permissions non-static content
406 if cmd != 'static':
386 if cmd != 'static':
407 self.check_perm(rctx, req, None)
387 self.check_perm(rctx, req, None)
408
388
409 if cmd == '':
389 if cmd == '':
410 req.form['cmd'] = [tmpl.cache['default']]
390 req.form['cmd'] = [tmpl.cache['default']]
411 cmd = req.form['cmd'][0]
391 cmd = req.form['cmd'][0]
412
392
413 if rctx.configbool('web', 'cache', True):
393 if rctx.configbool('web', 'cache', True):
414 caching(self, req) # sets ETag header or raises NOT_MODIFIED
394 caching(self, req) # sets ETag header or raises NOT_MODIFIED
415 if cmd not in webcommands.__all__:
395 if cmd not in webcommands.__all__:
416 msg = 'no such method: %s' % cmd
396 msg = 'no such method: %s' % cmd
417 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
397 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
418 elif cmd == 'file' and 'raw' in req.form.get('style', []):
398 elif cmd == 'file' and 'raw' in req.form.get('style', []):
419 self.ctype = ctype
399 self.ctype = ctype
420 content = webcommands.rawfile(rctx, req, tmpl)
400 content = webcommands.rawfile(rctx, req, tmpl)
421 else:
401 else:
422 content = getattr(webcommands, cmd)(rctx, req, tmpl)
402 content = getattr(webcommands, cmd)(rctx, req, tmpl)
423 req.respond(HTTP_OK, ctype)
403 req.respond(HTTP_OK, ctype)
424
404
425 return content
405 return content
426
406
427 except (error.LookupError, error.RepoLookupError) as err:
407 except (error.LookupError, error.RepoLookupError) as err:
428 req.respond(HTTP_NOT_FOUND, ctype)
408 req.respond(HTTP_NOT_FOUND, ctype)
429 msg = str(err)
409 msg = str(err)
430 if (util.safehasattr(err, 'name') and
410 if (util.safehasattr(err, 'name') and
431 not isinstance(err, error.ManifestLookupError)):
411 not isinstance(err, error.ManifestLookupError)):
432 msg = 'revision not found: %s' % err.name
412 msg = 'revision not found: %s' % err.name
433 return tmpl('error', error=msg)
413 return tmpl('error', error=msg)
434 except (error.RepoError, error.RevlogError) as inst:
414 except (error.RepoError, error.RevlogError) as inst:
435 req.respond(HTTP_SERVER_ERROR, ctype)
415 req.respond(HTTP_SERVER_ERROR, ctype)
436 return tmpl('error', error=str(inst))
416 return tmpl('error', error=str(inst))
437 except ErrorResponse as inst:
417 except ErrorResponse as inst:
438 req.respond(inst, ctype)
418 req.respond(inst, ctype)
439 if inst.code == HTTP_NOT_MODIFIED:
419 if inst.code == HTTP_NOT_MODIFIED:
440 # Not allowed to return a body on a 304
420 # Not allowed to return a body on a 304
441 return ['']
421 return ['']
442 return tmpl('error', error=str(inst))
422 return tmpl('error', error=str(inst))
443
423
444 def check_perm(self, rctx, req, op):
424 def check_perm(self, rctx, req, op):
445 for permhook in permhooks:
425 for permhook in permhooks:
446 permhook(rctx, req, op)
426 permhook(rctx, req, op)
427
428 def getwebview(repo):
429 """The 'web.view' config controls changeset filter to hgweb. Possible
430 values are ``served``, ``visible`` and ``all``. Default is ``served``.
431 The ``served`` filter only shows changesets that can be pulled from the
432 hgweb instance. The``visible`` filter includes secret changesets but
433 still excludes "hidden" one.
434
435 See the repoview module for details.
436
437 The option has been around undocumented since Mercurial 2.5, but no
438 user ever asked about it. So we better keep it undocumented for now."""
439 viewconfig = repo.ui.config('web', 'view', 'served',
440 untrusted=True)
441 if viewconfig == 'all':
442 return repo.unfiltered()
443 elif viewconfig in repoview.filtertable:
444 return repo.filtered(viewconfig)
445 else:
446 return repo.filtered('served')
447
General Comments 0
You need to be logged in to leave comments. Login now