##// END OF EJS Templates
hgweb: kill parentui references
Matt Mackall -
r8191:35604226 default
parent child Browse files
Show More
@@ -1,161 +1,161
1 1 # zeroconf.py - zeroconf support for Mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of
6 6 # the GNU General Public License (version 2), incorporated herein by
7 7 # reference.
8 8
9 9 '''zeroconf support for mercurial repositories
10 10
11 11 Zeroconf enabled repositories will be announced in a network without
12 12 the need to configure a server or a service. They can be discovered
13 13 without knowing their actual IP address.
14 14
15 15 To use the zeroconf extension add the following entry to your hgrc
16 16 file:
17 17
18 18 [extensions]
19 19 hgext.zeroconf =
20 20
21 21 To allow other people to discover your repository using run "hg serve"
22 22 in your repository.
23 23
24 24 $ cd test
25 25 $ hg serve
26 26
27 27 You can discover zeroconf enabled repositories by running "hg paths".
28 28
29 29 $ hg paths
30 30 zc-test = http://example.com:8000/test
31 31 '''
32 32
33 33 import Zeroconf, socket, time, os
34 34 from mercurial import ui
35 35 from mercurial import extensions
36 36 from mercurial.hgweb import hgweb_mod
37 37 from mercurial.hgweb import hgwebdir_mod
38 38
39 39 # publish
40 40
41 41 server = None
42 42 localip = None
43 43
44 44 def getip():
45 45 # finds external-facing interface without sending any packets (Linux)
46 46 try:
47 47 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
48 48 s.connect(('1.0.0.1', 0))
49 49 ip = s.getsockname()[0]
50 50 return ip
51 51 except:
52 52 pass
53 53
54 54 # Generic method, sometimes gives useless results
55 55 dumbip = socket.gethostbyaddr(socket.gethostname())[2][0]
56 56 if not dumbip.startswith('127.') and ':' not in dumbip:
57 57 return dumbip
58 58
59 59 # works elsewhere, but actually sends a packet
60 60 try:
61 61 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
62 62 s.connect(('1.0.0.1', 1))
63 63 ip = s.getsockname()[0]
64 64 return ip
65 65 except:
66 66 pass
67 67
68 68 return dumbip
69 69
70 70 def publish(name, desc, path, port):
71 71 global server, localip
72 72 if not server:
73 73 try:
74 74 server = Zeroconf.Zeroconf()
75 75 except socket.gaierror:
76 76 # if we have no internet connection, this can happen.
77 77 return
78 78 ip = getip()
79 79 localip = socket.inet_aton(ip)
80 80
81 81 hostname = socket.gethostname().split('.')[0]
82 82 host = hostname + ".local"
83 83 name = "%s-%s" % (hostname, name)
84 84
85 85 # advertise to browsers
86 86 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
87 87 name + '._http._tcp.local.',
88 88 server = host,
89 89 port = port,
90 90 properties = {'description': desc,
91 91 'path': "/" + path},
92 92 address = localip, weight = 0, priority = 0)
93 93 server.registerService(svc)
94 94
95 95 # advertise to Mercurial clients
96 96 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
97 97 name + '._hg._tcp.local.',
98 98 server = host,
99 99 port = port,
100 100 properties = {'description': desc,
101 101 'path': "/" + path},
102 102 address = localip, weight = 0, priority = 0)
103 103 server.registerService(svc)
104 104
105 105 class hgwebzc(hgweb_mod.hgweb):
106 106 def __init__(self, repo, name=None):
107 107 super(hgwebzc, self).__init__(repo, name)
108 108 name = self.reponame or os.path.basename(repo.root)
109 109 desc = self.repo.ui.config("web", "description", name)
110 110 publish(name, desc, name, int(repo.ui.config("web", "port", 8000)))
111 111
112 112 class hgwebdirzc(hgwebdir_mod.hgwebdir):
113 113 def run(self):
114 114 for r, p in self.repos:
115 u = self.parentui.copy()
115 u = self.ui.copy()
116 116 u.readconfig(os.path.join(p, '.hg', 'hgrc'))
117 117 n = os.path.basename(r)
118 118 publish(n, "hgweb", p, int(u.config("web", "port", 8000)))
119 119 return super(hgwebdirzc, self).run()
120 120
121 121 # listen
122 122
123 123 class listener(object):
124 124 def __init__(self):
125 125 self.found = {}
126 126 def removeService(self, server, type, name):
127 127 if repr(name) in self.found:
128 128 del self.found[repr(name)]
129 129 def addService(self, server, type, name):
130 130 self.found[repr(name)] = server.getServiceInfo(type, name)
131 131
132 132 def getzcpaths():
133 133 server = Zeroconf.Zeroconf()
134 134 l = listener()
135 135 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
136 136 time.sleep(1)
137 137 server.close()
138 138 for v in l.found.values():
139 139 n = v.name[:v.name.index('.')]
140 140 n.replace(" ", "-")
141 141 u = "http://%s:%s%s" % (socket.inet_ntoa(v.address), v.port,
142 142 v.properties.get("path", "/"))
143 143 yield "zc-" + n, u
144 144
145 145 def config(orig, self, section, key, default=None, untrusted=False):
146 146 if section == "paths" and key.startswith("zc-"):
147 147 for n, p in getzcpaths():
148 148 if n == key:
149 149 return p
150 150 return orig(self, section, key, default, untrusted)
151 151
152 152 def configitems(orig, self, section, untrusted=False):
153 153 r = orig(self, section, untrusted)
154 154 if section == "paths":
155 155 r += getzcpaths()
156 156 return r
157 157
158 158 extensions.wrapfunction(ui.ui, 'config', config)
159 159 extensions.wrapfunction(ui.ui, 'configitems', configitems)
160 160 hgweb_mod.hgweb = hgwebzc
161 161 hgwebdir_mod.hgwebdir = hgwebdirzc
@@ -1,312 +1,312
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial import ui, hg, util, hook, error, encoding
11 11 from mercurial import templater, templatefilters
12 12 from common import get_mtime, ErrorResponse
13 13 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 14 from common import HTTP_UNAUTHORIZED, HTTP_METHOD_NOT_ALLOWED
15 15 from request import wsgirequest
16 16 import webcommands, protocol, webutil
17 17
18 18 perms = {
19 19 'changegroup': 'pull',
20 20 'changegroupsubset': 'pull',
21 21 'unbundle': 'push',
22 22 'stream_out': 'pull',
23 23 }
24 24
25 25 class hgweb(object):
26 26 def __init__(self, repo, name=None):
27 27 if isinstance(repo, str):
28 parentui = ui.ui()
29 parentui.setconfig('ui', 'report_untrusted', 'off')
30 parentui.setconfig('ui', 'interactive', 'off')
31 self.repo = hg.repository(parentui, repo)
28 u = ui.ui()
29 u.setconfig('ui', 'report_untrusted', 'off')
30 u.setconfig('ui', 'interactive', 'off')
31 self.repo = hg.repository(u, repo)
32 32 else:
33 33 self.repo = repo
34 34
35 35 hook.redirect(True)
36 36 self.mtime = -1
37 37 self.reponame = name
38 38 self.archives = 'zip', 'gz', 'bz2'
39 39 self.stripecount = 1
40 40 # a repo owner may set web.templates in .hg/hgrc to get any file
41 41 # readable by the user running the CGI script
42 42 self.templatepath = self.config('web', 'templates')
43 43
44 44 # The CGI scripts are often run by a user different from the repo owner.
45 45 # Trust the settings from the .hg/hgrc files by default.
46 46 def config(self, section, name, default=None, untrusted=True):
47 47 return self.repo.ui.config(section, name, default,
48 48 untrusted=untrusted)
49 49
50 50 def configbool(self, section, name, default=False, untrusted=True):
51 51 return self.repo.ui.configbool(section, name, default,
52 52 untrusted=untrusted)
53 53
54 54 def configlist(self, section, name, default=None, untrusted=True):
55 55 return self.repo.ui.configlist(section, name, default,
56 56 untrusted=untrusted)
57 57
58 58 def refresh(self):
59 59 mtime = get_mtime(self.repo.root)
60 60 if mtime != self.mtime:
61 61 self.mtime = mtime
62 62 self.repo = hg.repository(self.repo.ui, self.repo.root)
63 63 self.maxchanges = int(self.config("web", "maxchanges", 10))
64 64 self.stripecount = int(self.config("web", "stripes", 1))
65 65 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
66 66 self.maxfiles = int(self.config("web", "maxfiles", 10))
67 67 self.allowpull = self.configbool("web", "allowpull", True)
68 68 self.encoding = self.config("web", "encoding", encoding.encoding)
69 69
70 70 def run(self):
71 71 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
72 72 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
73 73 import mercurial.hgweb.wsgicgi as wsgicgi
74 74 wsgicgi.launch(self)
75 75
76 76 def __call__(self, env, respond):
77 77 req = wsgirequest(env, respond)
78 78 return self.run_wsgi(req)
79 79
80 80 def run_wsgi(self, req):
81 81
82 82 self.refresh()
83 83
84 84 # process this if it's a protocol request
85 85 # protocol bits don't need to create any URLs
86 86 # and the clients always use the old URL structure
87 87
88 88 cmd = req.form.get('cmd', [''])[0]
89 89 if cmd and cmd in protocol.__all__:
90 90 try:
91 91 if cmd in perms:
92 92 try:
93 93 self.check_perm(req, perms[cmd])
94 94 except ErrorResponse, inst:
95 95 if cmd == 'unbundle':
96 96 req.drain()
97 97 raise
98 98 method = getattr(protocol, cmd)
99 99 return method(self.repo, req)
100 100 except ErrorResponse, inst:
101 101 req.respond(inst, protocol.HGTYPE)
102 102 if not inst.message:
103 103 return []
104 104 return '0\n%s\n' % inst.message,
105 105
106 106 # work with CGI variables to create coherent structure
107 107 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
108 108
109 109 req.url = req.env['SCRIPT_NAME']
110 110 if not req.url.endswith('/'):
111 111 req.url += '/'
112 112 if 'REPO_NAME' in req.env:
113 113 req.url += req.env['REPO_NAME'] + '/'
114 114
115 115 if 'PATH_INFO' in req.env:
116 116 parts = req.env['PATH_INFO'].strip('/').split('/')
117 117 repo_parts = req.env.get('REPO_NAME', '').split('/')
118 118 if parts[:len(repo_parts)] == repo_parts:
119 119 parts = parts[len(repo_parts):]
120 120 query = '/'.join(parts)
121 121 else:
122 122 query = req.env['QUERY_STRING'].split('&', 1)[0]
123 123 query = query.split(';', 1)[0]
124 124
125 125 # translate user-visible url structure to internal structure
126 126
127 127 args = query.split('/', 2)
128 128 if 'cmd' not in req.form and args and args[0]:
129 129
130 130 cmd = args.pop(0)
131 131 style = cmd.rfind('-')
132 132 if style != -1:
133 133 req.form['style'] = [cmd[:style]]
134 134 cmd = cmd[style+1:]
135 135
136 136 # avoid accepting e.g. style parameter as command
137 137 if hasattr(webcommands, cmd):
138 138 req.form['cmd'] = [cmd]
139 139 else:
140 140 cmd = ''
141 141
142 142 if cmd == 'static':
143 143 req.form['file'] = ['/'.join(args)]
144 144 else:
145 145 if args and args[0]:
146 146 node = args.pop(0)
147 147 req.form['node'] = [node]
148 148 if args:
149 149 req.form['file'] = args
150 150
151 151 if cmd == 'archive':
152 152 fn = req.form['node'][0]
153 153 for type_, spec in self.archive_specs.iteritems():
154 154 ext = spec[2]
155 155 if fn.endswith(ext):
156 156 req.form['node'] = [fn[:-len(ext)]]
157 157 req.form['type'] = [type_]
158 158
159 159 # process the web interface request
160 160
161 161 try:
162 162 tmpl = self.templater(req)
163 163 ctype = tmpl('mimetype', encoding=self.encoding)
164 164 ctype = templater.stringify(ctype)
165 165
166 166 # check read permissions non-static content
167 167 if cmd != 'static':
168 168 self.check_perm(req, None)
169 169
170 170 if cmd == '':
171 171 req.form['cmd'] = [tmpl.cache['default']]
172 172 cmd = req.form['cmd'][0]
173 173
174 174 if cmd not in webcommands.__all__:
175 175 msg = 'no such method: %s' % cmd
176 176 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
177 177 elif cmd == 'file' and 'raw' in req.form.get('style', []):
178 178 self.ctype = ctype
179 179 content = webcommands.rawfile(self, req, tmpl)
180 180 else:
181 181 content = getattr(webcommands, cmd)(self, req, tmpl)
182 182 req.respond(HTTP_OK, ctype)
183 183
184 184 return content
185 185
186 186 except error.LookupError, err:
187 187 req.respond(HTTP_NOT_FOUND, ctype)
188 188 msg = str(err)
189 189 if 'manifest' not in msg:
190 190 msg = 'revision not found: %s' % err.name
191 191 return tmpl('error', error=msg)
192 192 except (error.RepoError, error.RevlogError), inst:
193 193 req.respond(HTTP_SERVER_ERROR, ctype)
194 194 return tmpl('error', error=str(inst))
195 195 except ErrorResponse, inst:
196 196 req.respond(inst, ctype)
197 197 return tmpl('error', error=inst.message)
198 198
199 199 def templater(self, req):
200 200
201 201 # determine scheme, port and server name
202 202 # this is needed to create absolute urls
203 203
204 204 proto = req.env.get('wsgi.url_scheme')
205 205 if proto == 'https':
206 206 proto = 'https'
207 207 default_port = "443"
208 208 else:
209 209 proto = 'http'
210 210 default_port = "80"
211 211
212 212 port = req.env["SERVER_PORT"]
213 213 port = port != default_port and (":" + port) or ""
214 214 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
215 215 staticurl = self.config("web", "staticurl") or req.url + 'static/'
216 216 if not staticurl.endswith('/'):
217 217 staticurl += '/'
218 218
219 219 # some functions for the templater
220 220
221 221 def header(**map):
222 222 yield tmpl('header', encoding=self.encoding, **map)
223 223
224 224 def footer(**map):
225 225 yield tmpl("footer", **map)
226 226
227 227 def motd(**map):
228 228 yield self.config("web", "motd", "")
229 229
230 230 # figure out which style to use
231 231
232 232 vars = {}
233 233 style = self.config("web", "style", "paper")
234 234 if 'style' in req.form:
235 235 style = req.form['style'][0]
236 236 vars['style'] = style
237 237
238 238 start = req.url[-1] == '?' and '&' or '?'
239 239 sessionvars = webutil.sessionvars(vars, start)
240 240 mapfile = templater.stylemap(style, self.templatepath)
241 241
242 242 if not self.reponame:
243 243 self.reponame = (self.config("web", "name")
244 244 or req.env.get('REPO_NAME')
245 245 or req.url.strip('/') or self.repo.root)
246 246
247 247 # create the templater
248 248
249 249 tmpl = templater.templater(mapfile, templatefilters.filters,
250 250 defaults={"url": req.url,
251 251 "staticurl": staticurl,
252 252 "urlbase": urlbase,
253 253 "repo": self.reponame,
254 254 "header": header,
255 255 "footer": footer,
256 256 "motd": motd,
257 257 "sessionvars": sessionvars
258 258 })
259 259 return tmpl
260 260
261 261 def archivelist(self, nodeid):
262 262 allowed = self.configlist("web", "allow_archive")
263 263 for i, spec in self.archive_specs.iteritems():
264 264 if i in allowed or self.configbool("web", "allow" + i):
265 265 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
266 266
267 267 archive_specs = {
268 268 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
269 269 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
270 270 'zip': ('application/zip', 'zip', '.zip', None),
271 271 }
272 272
273 273 def check_perm(self, req, op):
274 274 '''Check permission for operation based on request data (including
275 275 authentication info). Return if op allowed, else raise an ErrorResponse
276 276 exception.'''
277 277
278 278 user = req.env.get('REMOTE_USER')
279 279
280 280 deny_read = self.configlist('web', 'deny_read')
281 281 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
282 282 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
283 283
284 284 allow_read = self.configlist('web', 'allow_read')
285 285 result = (not allow_read) or (allow_read == ['*'])
286 286 if not (result or user in allow_read):
287 287 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
288 288
289 289 if op == 'pull' and not self.allowpull:
290 290 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
291 291 elif op == 'pull' or op is None: # op is None for interface requests
292 292 return
293 293
294 294 # enforce that you can only push using POST requests
295 295 if req.env['REQUEST_METHOD'] != 'POST':
296 296 msg = 'push requires POST request'
297 297 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
298 298
299 299 # require ssl by default for pushing, auth info cannot be sniffed
300 300 # and replayed
301 301 scheme = req.env.get('wsgi.url_scheme')
302 302 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
303 303 raise ErrorResponse(HTTP_OK, 'ssl required')
304 304
305 305 deny = self.configlist('web', 'deny_push')
306 306 if deny and (not user or deny == ['*'] or user in deny):
307 307 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
308 308
309 309 allow = self.configlist('web', 'allow_push')
310 310 result = allow and (allow == ['*'] or user in allow)
311 311 if not result:
312 312 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
@@ -1,326 +1,326
1 1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 from mercurial.i18n import _
11 11 from mercurial import ui, hg, util, templater, templatefilters
12 12 from mercurial import config, error, encoding
13 13 from common import ErrorResponse, get_mtime, staticfile, paritygen,\
14 14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 15 from hgweb_mod import hgweb
16 16 from request import wsgirequest
17 17
18 18 # This is a stopgap
19 19 class hgwebdir(object):
20 def __init__(self, conf, parentui=None):
20 def __init__(self, conf, baseui=None):
21 21 def cleannames(items):
22 22 return [(util.pconvert(name).strip('/'), path)
23 23 for name, path in items]
24 24
25 if parentui:
26 self.parentui = parentui
25 if baseui:
26 self.ui = baseui.copy()
27 27 else:
28 self.parentui = ui.ui()
29 self.parentui.setconfig('ui', 'report_untrusted', 'off')
30 self.parentui.setconfig('ui', 'interactive', 'off')
28 self.ui = ui.ui()
29 self.ui.setconfig('ui', 'report_untrusted', 'off')
30 self.ui.setconfig('ui', 'interactive', 'off')
31 31
32 32 self.motd = None
33 33 self.style = 'paper'
34 34 self.stripecount = None
35 35 self.repos_sorted = ('name', False)
36 36 self._baseurl = None
37 37 if isinstance(conf, (list, tuple)):
38 38 self.repos = cleannames(conf)
39 39 self.repos_sorted = ('', False)
40 40 elif isinstance(conf, dict):
41 41 self.repos = util.sort(cleannames(conf.items()))
42 42 else:
43 43 if isinstance(conf, config.config):
44 44 cp = conf
45 45 else:
46 46 cp = config.config()
47 47 cp.read(conf)
48 48 self.repos = []
49 49 self.motd = cp.get('web', 'motd')
50 50 self.style = cp.get('web', 'style', 'paper')
51 51 self.stripecount = cp.get('web', 'stripes')
52 52 self._baseurl = cp.get('web', 'baseurl')
53 53 if 'paths' in cp:
54 54 paths = cleannames(cp.items('paths'))
55 55 for prefix, root in paths:
56 56 roothead, roottail = os.path.split(root)
57 57 # "foo = /bar/*" makes every subrepo of /bar/ to be
58 58 # mounted as foo/subrepo
59 59 # and "foo = /bar/**" does even recurse inside the
60 60 # subdirectories, remember to use it without working dir.
61 61 try:
62 62 recurse = {'*': False, '**': True}[roottail]
63 63 except KeyError:
64 64 self.repos.append((prefix, root))
65 65 continue
66 66 roothead = os.path.normpath(roothead)
67 67 for path in util.walkrepos(roothead, followsym=True,
68 68 recurse=recurse):
69 69 path = os.path.normpath(path)
70 70 name = util.pconvert(path[len(roothead):]).strip('/')
71 71 if prefix:
72 72 name = prefix + '/' + name
73 73 self.repos.append((name, path))
74 74 for prefix, root in cp.items('collections'):
75 75 for path in util.walkrepos(root, followsym=True):
76 76 repo = os.path.normpath(path)
77 77 name = repo
78 78 if name.startswith(prefix):
79 79 name = name[len(prefix):]
80 80 self.repos.append((name.lstrip(os.sep), repo))
81 81 self.repos.sort()
82 82
83 83 def run(self):
84 84 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
85 85 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
86 86 import mercurial.hgweb.wsgicgi as wsgicgi
87 87 wsgicgi.launch(self)
88 88
89 89 def __call__(self, env, respond):
90 90 req = wsgirequest(env, respond)
91 91 return self.run_wsgi(req)
92 92
93 93 def read_allowed(self, ui, req):
94 94 """Check allow_read and deny_read config options of a repo's ui object
95 95 to determine user permissions. By default, with neither option set (or
96 96 both empty), allow all users to read the repo. There are two ways a
97 97 user can be denied read access: (1) deny_read is not empty, and the
98 98 user is unauthenticated or deny_read contains user (or *), and (2)
99 99 allow_read is not empty and the user is not in allow_read. Return True
100 100 if user is allowed to read the repo, else return False."""
101 101
102 102 user = req.env.get('REMOTE_USER')
103 103
104 104 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
105 105 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
106 106 return False
107 107
108 108 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
109 109 # by default, allow reading if no allow_read option has been set
110 110 if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
111 111 return True
112 112
113 113 return False
114 114
115 115 def run_wsgi(self, req):
116 116
117 117 try:
118 118 try:
119 119
120 120 virtual = req.env.get("PATH_INFO", "").strip('/')
121 121 tmpl = self.templater(req)
122 122 ctype = tmpl('mimetype', encoding=encoding.encoding)
123 123 ctype = templater.stringify(ctype)
124 124
125 125 # a static file
126 126 if virtual.startswith('static/') or 'static' in req.form:
127 127 if virtual.startswith('static/'):
128 128 fname = virtual[7:]
129 129 else:
130 130 fname = req.form['static'][0]
131 131 static = templater.templatepath('static')
132 132 return (staticfile(static, fname, req),)
133 133
134 134 # top-level index
135 135 elif not virtual:
136 136 req.respond(HTTP_OK, ctype)
137 137 return self.makeindex(req, tmpl)
138 138
139 139 # nested indexes and hgwebs
140 140
141 141 repos = dict(self.repos)
142 142 while virtual:
143 143 real = repos.get(virtual)
144 144 if real:
145 145 req.env['REPO_NAME'] = virtual
146 146 try:
147 repo = hg.repository(self.parentui, real)
147 repo = hg.repository(self.ui, real)
148 148 return hgweb(repo).run_wsgi(req)
149 149 except IOError, inst:
150 150 msg = inst.strerror
151 151 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
152 152 except error.RepoError, inst:
153 153 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
154 154
155 155 # browse subdirectories
156 156 subdir = virtual + '/'
157 157 if [r for r in repos if r.startswith(subdir)]:
158 158 req.respond(HTTP_OK, ctype)
159 159 return self.makeindex(req, tmpl, subdir)
160 160
161 161 up = virtual.rfind('/')
162 162 if up < 0:
163 163 break
164 164 virtual = virtual[:up]
165 165
166 166 # prefixes not found
167 167 req.respond(HTTP_NOT_FOUND, ctype)
168 168 return tmpl("notfound", repo=virtual)
169 169
170 170 except ErrorResponse, err:
171 171 req.respond(err, ctype)
172 172 return tmpl('error', error=err.message or '')
173 173 finally:
174 174 tmpl = None
175 175
176 176 def makeindex(self, req, tmpl, subdir=""):
177 177
178 178 def archivelist(ui, nodeid, url):
179 179 allowed = ui.configlist("web", "allow_archive", untrusted=True)
180 180 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
181 181 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
182 182 untrusted=True):
183 183 yield {"type" : i[0], "extension": i[1],
184 184 "node": nodeid, "url": url}
185 185
186 186 def entries(sortcolumn="", descending=False, subdir="", **map):
187 187 def sessionvars(**map):
188 188 fields = []
189 189 if 'style' in req.form:
190 190 style = req.form['style'][0]
191 191 if style != get('web', 'style', ''):
192 192 fields.append(('style', style))
193 193
194 194 separator = url[-1] == '?' and ';' or '?'
195 195 for name, value in fields:
196 196 yield dict(name=name, value=value, separator=separator)
197 197 separator = ';'
198 198
199 199 rows = []
200 200 parity = paritygen(self.stripecount)
201 201 for name, path in self.repos:
202 202 if not name.startswith(subdir):
203 203 continue
204 204 name = name[len(subdir):]
205 205
206 u = self.parentui.copy()
206 u = self.ui.copy()
207 207 try:
208 208 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
209 209 except Exception, e:
210 210 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
211 211 continue
212 212 def get(section, name, default=None):
213 213 return u.config(section, name, default, untrusted=True)
214 214
215 215 if u.configbool("web", "hidden", untrusted=True):
216 216 continue
217 217
218 218 if not self.read_allowed(u, req):
219 219 continue
220 220
221 221 parts = [name]
222 222 if 'PATH_INFO' in req.env:
223 223 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
224 224 if req.env['SCRIPT_NAME']:
225 225 parts.insert(0, req.env['SCRIPT_NAME'])
226 226 url = ('/'.join(parts).replace("//", "/")) + '/'
227 227
228 228 # update time with local timezone
229 229 try:
230 230 d = (get_mtime(path), util.makedate()[1])
231 231 except OSError:
232 232 continue
233 233
234 234 contact = get_contact(get)
235 235 description = get("web", "description", "")
236 236 name = get("web", "name", name)
237 237 row = dict(contact=contact or "unknown",
238 238 contact_sort=contact.upper() or "unknown",
239 239 name=name,
240 240 name_sort=name,
241 241 url=url,
242 242 description=description or "unknown",
243 243 description_sort=description.upper() or "unknown",
244 244 lastchange=d,
245 245 lastchange_sort=d[1]-d[0],
246 246 sessionvars=sessionvars,
247 247 archives=archivelist(u, "tip", url))
248 248 if (not sortcolumn
249 249 or (sortcolumn, descending) == self.repos_sorted):
250 250 # fast path for unsorted output
251 251 row['parity'] = parity.next()
252 252 yield row
253 253 else:
254 254 rows.append((row["%s_sort" % sortcolumn], row))
255 255 if rows:
256 256 rows.sort()
257 257 if descending:
258 258 rows.reverse()
259 259 for key, row in rows:
260 260 row['parity'] = parity.next()
261 261 yield row
262 262
263 263 sortable = ["name", "description", "contact", "lastchange"]
264 264 sortcolumn, descending = self.repos_sorted
265 265 if 'sort' in req.form:
266 266 sortcolumn = req.form['sort'][0]
267 267 descending = sortcolumn.startswith('-')
268 268 if descending:
269 269 sortcolumn = sortcolumn[1:]
270 270 if sortcolumn not in sortable:
271 271 sortcolumn = ""
272 272
273 273 sort = [("sort_%s" % column,
274 274 "%s%s" % ((not descending and column == sortcolumn)
275 275 and "-" or "", column))
276 276 for column in sortable]
277 277
278 278 if self._baseurl is not None:
279 279 req.env['SCRIPT_NAME'] = self._baseurl
280 280
281 281 return tmpl("index", entries=entries, subdir=subdir,
282 282 sortcolumn=sortcolumn, descending=descending,
283 283 **dict(sort))
284 284
285 285 def templater(self, req):
286 286
287 287 def header(**map):
288 288 yield tmpl('header', encoding=encoding.encoding, **map)
289 289
290 290 def footer(**map):
291 291 yield tmpl("footer", **map)
292 292
293 293 def motd(**map):
294 294 if self.motd is not None:
295 295 yield self.motd
296 296 else:
297 297 yield config('web', 'motd', '')
298 298
299 299 def config(section, name, default=None, untrusted=True):
300 return self.parentui.config(section, name, default, untrusted)
300 return self.ui.config(section, name, default, untrusted)
301 301
302 302 if self._baseurl is not None:
303 303 req.env['SCRIPT_NAME'] = self._baseurl
304 304
305 305 url = req.env.get('SCRIPT_NAME', '')
306 306 if not url.endswith('/'):
307 307 url += '/'
308 308
309 309 staticurl = config('web', 'staticurl') or url + 'static/'
310 310 if not staticurl.endswith('/'):
311 311 staticurl += '/'
312 312
313 313 style = self.style
314 314 if style is None:
315 315 style = config('web', 'style', '')
316 316 if 'style' in req.form:
317 317 style = req.form['style'][0]
318 318 self.stripecount = int(self.stripecount or config('web', 'stripes', 1))
319 319 mapfile = templater.stylemap(style)
320 320 tmpl = templater.templater(mapfile, templatefilters.filters,
321 321 defaults={"header": header,
322 322 "footer": footer,
323 323 "motd": motd,
324 324 "url": url,
325 325 "staticurl": staticurl})
326 326 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now