##// END OF EJS Templates
hgwebdir: normalize virtual paths before stripping the separator...
Patrick Mezard -
r5584:d2831a5d default
parent child Browse files
Show More
@@ -1,260 +1,260 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetools, cStringIO
9 import os, mimetools, cStringIO
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from common import get_mtime, staticfile, style_map, paritygen
12 from common import get_mtime, staticfile, style_map, paritygen
13 from hgweb_mod import hgweb
13 from hgweb_mod import hgweb
14
14
15 # This is a stopgap
15 # This is a stopgap
16 class hgwebdir(object):
16 class hgwebdir(object):
17 def __init__(self, config, parentui=None):
17 def __init__(self, config, parentui=None):
18 def cleannames(items):
18 def cleannames(items):
19 return [(util.pconvert(name.strip(os.sep)), path)
19 return [(util.pconvert(name).strip('/'), path)
20 for name, path in items]
20 for name, path in items]
21
21
22 self.parentui = parentui
22 self.parentui = parentui
23 self.motd = None
23 self.motd = None
24 self.style = None
24 self.style = None
25 self.stripecount = None
25 self.stripecount = None
26 self.repos_sorted = ('name', False)
26 self.repos_sorted = ('name', False)
27 if isinstance(config, (list, tuple)):
27 if isinstance(config, (list, tuple)):
28 self.repos = cleannames(config)
28 self.repos = cleannames(config)
29 self.repos_sorted = ('', False)
29 self.repos_sorted = ('', False)
30 elif isinstance(config, dict):
30 elif isinstance(config, dict):
31 self.repos = cleannames(config.items())
31 self.repos = cleannames(config.items())
32 self.repos.sort()
32 self.repos.sort()
33 else:
33 else:
34 if isinstance(config, util.configparser):
34 if isinstance(config, util.configparser):
35 cp = config
35 cp = config
36 else:
36 else:
37 cp = util.configparser()
37 cp = util.configparser()
38 cp.read(config)
38 cp.read(config)
39 self.repos = []
39 self.repos = []
40 if cp.has_section('web'):
40 if cp.has_section('web'):
41 if cp.has_option('web', 'motd'):
41 if cp.has_option('web', 'motd'):
42 self.motd = cp.get('web', 'motd')
42 self.motd = cp.get('web', 'motd')
43 if cp.has_option('web', 'style'):
43 if cp.has_option('web', 'style'):
44 self.style = cp.get('web', 'style')
44 self.style = cp.get('web', 'style')
45 if cp.has_option('web', 'stripes'):
45 if cp.has_option('web', 'stripes'):
46 self.stripecount = int(cp.get('web', 'stripes'))
46 self.stripecount = int(cp.get('web', 'stripes'))
47 if cp.has_section('paths'):
47 if cp.has_section('paths'):
48 self.repos.extend(cleannames(cp.items('paths')))
48 self.repos.extend(cleannames(cp.items('paths')))
49 if cp.has_section('collections'):
49 if cp.has_section('collections'):
50 for prefix, root in cp.items('collections'):
50 for prefix, root in cp.items('collections'):
51 for path in util.walkrepos(root):
51 for path in util.walkrepos(root):
52 repo = os.path.normpath(path)
52 repo = os.path.normpath(path)
53 name = repo
53 name = repo
54 if name.startswith(prefix):
54 if name.startswith(prefix):
55 name = name[len(prefix):]
55 name = name[len(prefix):]
56 self.repos.append((name.lstrip(os.sep), repo))
56 self.repos.append((name.lstrip(os.sep), repo))
57 self.repos.sort()
57 self.repos.sort()
58
58
59 def run(self):
59 def run(self):
60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
60 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
61 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 import mercurial.hgweb.wsgicgi as wsgicgi
62 import mercurial.hgweb.wsgicgi as wsgicgi
63 from request import wsgiapplication
63 from request import wsgiapplication
64 def make_web_app():
64 def make_web_app():
65 return self
65 return self
66 wsgicgi.launch(wsgiapplication(make_web_app))
66 wsgicgi.launch(wsgiapplication(make_web_app))
67
67
68 def run_wsgi(self, req):
68 def run_wsgi(self, req):
69 def header(**map):
69 def header(**map):
70 header_file = cStringIO.StringIO(
70 header_file = cStringIO.StringIO(
71 ''.join(tmpl("header", encoding=util._encoding, **map)))
71 ''.join(tmpl("header", encoding=util._encoding, **map)))
72 msg = mimetools.Message(header_file, 0)
72 msg = mimetools.Message(header_file, 0)
73 req.header(msg.items())
73 req.header(msg.items())
74 yield header_file.read()
74 yield header_file.read()
75
75
76 def footer(**map):
76 def footer(**map):
77 yield tmpl("footer", **map)
77 yield tmpl("footer", **map)
78
78
79 def motd(**map):
79 def motd(**map):
80 if self.motd is not None:
80 if self.motd is not None:
81 yield self.motd
81 yield self.motd
82 else:
82 else:
83 yield config('web', 'motd', '')
83 yield config('web', 'motd', '')
84
84
85 parentui = self.parentui or ui.ui(report_untrusted=False,
85 parentui = self.parentui or ui.ui(report_untrusted=False,
86 interactive=False)
86 interactive=False)
87
87
88 def config(section, name, default=None, untrusted=True):
88 def config(section, name, default=None, untrusted=True):
89 return parentui.config(section, name, default, untrusted)
89 return parentui.config(section, name, default, untrusted)
90
90
91 url = req.env['REQUEST_URI'].split('?')[0]
91 url = req.env['REQUEST_URI'].split('?')[0]
92 if not url.endswith('/'):
92 if not url.endswith('/'):
93 url += '/'
93 url += '/'
94 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
94 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
95 base = url[:len(url) - len(pathinfo)]
95 base = url[:len(url) - len(pathinfo)]
96 if not base.endswith('/'):
96 if not base.endswith('/'):
97 base += '/'
97 base += '/'
98
98
99 staticurl = config('web', 'staticurl') or base + 'static/'
99 staticurl = config('web', 'staticurl') or base + 'static/'
100 if not staticurl.endswith('/'):
100 if not staticurl.endswith('/'):
101 staticurl += '/'
101 staticurl += '/'
102
102
103 style = self.style
103 style = self.style
104 if style is None:
104 if style is None:
105 style = config('web', 'style', '')
105 style = config('web', 'style', '')
106 if req.form.has_key('style'):
106 if req.form.has_key('style'):
107 style = req.form['style'][0]
107 style = req.form['style'][0]
108 if self.stripecount is None:
108 if self.stripecount is None:
109 self.stripecount = int(config('web', 'stripes', 1))
109 self.stripecount = int(config('web', 'stripes', 1))
110 mapfile = style_map(templater.templatepath(), style)
110 mapfile = style_map(templater.templatepath(), style)
111 tmpl = templater.templater(mapfile, templater.common_filters,
111 tmpl = templater.templater(mapfile, templater.common_filters,
112 defaults={"header": header,
112 defaults={"header": header,
113 "footer": footer,
113 "footer": footer,
114 "motd": motd,
114 "motd": motd,
115 "url": url,
115 "url": url,
116 "staticurl": staticurl})
116 "staticurl": staticurl})
117
117
118 def archivelist(ui, nodeid, url):
118 def archivelist(ui, nodeid, url):
119 allowed = ui.configlist("web", "allow_archive", untrusted=True)
119 allowed = ui.configlist("web", "allow_archive", untrusted=True)
120 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
120 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
121 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
121 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
122 untrusted=True):
122 untrusted=True):
123 yield {"type" : i[0], "extension": i[1],
123 yield {"type" : i[0], "extension": i[1],
124 "node": nodeid, "url": url}
124 "node": nodeid, "url": url}
125
125
126 def entries(sortcolumn="", descending=False, subdir="", **map):
126 def entries(sortcolumn="", descending=False, subdir="", **map):
127 def sessionvars(**map):
127 def sessionvars(**map):
128 fields = []
128 fields = []
129 if req.form.has_key('style'):
129 if req.form.has_key('style'):
130 style = req.form['style'][0]
130 style = req.form['style'][0]
131 if style != get('web', 'style', ''):
131 if style != get('web', 'style', ''):
132 fields.append(('style', style))
132 fields.append(('style', style))
133
133
134 separator = url[-1] == '?' and ';' or '?'
134 separator = url[-1] == '?' and ';' or '?'
135 for name, value in fields:
135 for name, value in fields:
136 yield dict(name=name, value=value, separator=separator)
136 yield dict(name=name, value=value, separator=separator)
137 separator = ';'
137 separator = ';'
138
138
139 rows = []
139 rows = []
140 parity = paritygen(self.stripecount)
140 parity = paritygen(self.stripecount)
141 for name, path in self.repos:
141 for name, path in self.repos:
142 if not name.startswith(subdir):
142 if not name.startswith(subdir):
143 continue
143 continue
144 name = name[len(subdir):]
144 name = name[len(subdir):]
145
145
146 u = ui.ui(parentui=parentui)
146 u = ui.ui(parentui=parentui)
147 try:
147 try:
148 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
148 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
149 except Exception, e:
149 except Exception, e:
150 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
150 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
151 continue
151 continue
152 def get(section, name, default=None):
152 def get(section, name, default=None):
153 return u.config(section, name, default, untrusted=True)
153 return u.config(section, name, default, untrusted=True)
154
154
155 if u.configbool("web", "hidden", untrusted=True):
155 if u.configbool("web", "hidden", untrusted=True):
156 continue
156 continue
157
157
158 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
158 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
159 .replace("//", "/")) + '/'
159 .replace("//", "/")) + '/'
160
160
161 # update time with local timezone
161 # update time with local timezone
162 try:
162 try:
163 d = (get_mtime(path), util.makedate()[1])
163 d = (get_mtime(path), util.makedate()[1])
164 except OSError:
164 except OSError:
165 continue
165 continue
166
166
167 contact = (get("ui", "username") or # preferred
167 contact = (get("ui", "username") or # preferred
168 get("web", "contact") or # deprecated
168 get("web", "contact") or # deprecated
169 get("web", "author", "")) # also
169 get("web", "author", "")) # also
170 description = get("web", "description", "")
170 description = get("web", "description", "")
171 name = get("web", "name", name)
171 name = get("web", "name", name)
172 row = dict(contact=contact or "unknown",
172 row = dict(contact=contact or "unknown",
173 contact_sort=contact.upper() or "unknown",
173 contact_sort=contact.upper() or "unknown",
174 name=name,
174 name=name,
175 name_sort=name,
175 name_sort=name,
176 url=url,
176 url=url,
177 description=description or "unknown",
177 description=description or "unknown",
178 description_sort=description.upper() or "unknown",
178 description_sort=description.upper() or "unknown",
179 lastchange=d,
179 lastchange=d,
180 lastchange_sort=d[1]-d[0],
180 lastchange_sort=d[1]-d[0],
181 sessionvars=sessionvars,
181 sessionvars=sessionvars,
182 archives=archivelist(u, "tip", url))
182 archives=archivelist(u, "tip", url))
183 if (not sortcolumn
183 if (not sortcolumn
184 or (sortcolumn, descending) == self.repos_sorted):
184 or (sortcolumn, descending) == self.repos_sorted):
185 # fast path for unsorted output
185 # fast path for unsorted output
186 row['parity'] = parity.next()
186 row['parity'] = parity.next()
187 yield row
187 yield row
188 else:
188 else:
189 rows.append((row["%s_sort" % sortcolumn], row))
189 rows.append((row["%s_sort" % sortcolumn], row))
190 if rows:
190 if rows:
191 rows.sort()
191 rows.sort()
192 if descending:
192 if descending:
193 rows.reverse()
193 rows.reverse()
194 for key, row in rows:
194 for key, row in rows:
195 row['parity'] = parity.next()
195 row['parity'] = parity.next()
196 yield row
196 yield row
197
197
198 def makeindex(req, subdir=""):
198 def makeindex(req, subdir=""):
199 sortable = ["name", "description", "contact", "lastchange"]
199 sortable = ["name", "description", "contact", "lastchange"]
200 sortcolumn, descending = self.repos_sorted
200 sortcolumn, descending = self.repos_sorted
201 if req.form.has_key('sort'):
201 if req.form.has_key('sort'):
202 sortcolumn = req.form['sort'][0]
202 sortcolumn = req.form['sort'][0]
203 descending = sortcolumn.startswith('-')
203 descending = sortcolumn.startswith('-')
204 if descending:
204 if descending:
205 sortcolumn = sortcolumn[1:]
205 sortcolumn = sortcolumn[1:]
206 if sortcolumn not in sortable:
206 if sortcolumn not in sortable:
207 sortcolumn = ""
207 sortcolumn = ""
208
208
209 sort = [("sort_%s" % column,
209 sort = [("sort_%s" % column,
210 "%s%s" % ((not descending and column == sortcolumn)
210 "%s%s" % ((not descending and column == sortcolumn)
211 and "-" or "", column))
211 and "-" or "", column))
212 for column in sortable]
212 for column in sortable]
213 req.write(tmpl("index", entries=entries, subdir=subdir,
213 req.write(tmpl("index", entries=entries, subdir=subdir,
214 sortcolumn=sortcolumn, descending=descending,
214 sortcolumn=sortcolumn, descending=descending,
215 **dict(sort)))
215 **dict(sort)))
216
216
217 try:
217 try:
218 virtual = req.env.get("PATH_INFO", "").strip('/')
218 virtual = req.env.get("PATH_INFO", "").strip('/')
219 if virtual.startswith('static/'):
219 if virtual.startswith('static/'):
220 static = os.path.join(templater.templatepath(), 'static')
220 static = os.path.join(templater.templatepath(), 'static')
221 fname = virtual[7:]
221 fname = virtual[7:]
222 req.write(staticfile(static, fname, req) or
222 req.write(staticfile(static, fname, req) or
223 tmpl('error', error='%r not found' % fname))
223 tmpl('error', error='%r not found' % fname))
224 elif virtual:
224 elif virtual:
225 repos = dict(self.repos)
225 repos = dict(self.repos)
226 while virtual:
226 while virtual:
227 real = repos.get(virtual)
227 real = repos.get(virtual)
228 if real:
228 if real:
229 req.env['REPO_NAME'] = virtual
229 req.env['REPO_NAME'] = virtual
230 try:
230 try:
231 repo = hg.repository(parentui, real)
231 repo = hg.repository(parentui, real)
232 hgweb(repo).run_wsgi(req)
232 hgweb(repo).run_wsgi(req)
233 except IOError, inst:
233 except IOError, inst:
234 req.write(tmpl("error", error=inst.strerror))
234 req.write(tmpl("error", error=inst.strerror))
235 except hg.RepoError, inst:
235 except hg.RepoError, inst:
236 req.write(tmpl("error", error=str(inst)))
236 req.write(tmpl("error", error=str(inst)))
237 return
237 return
238
238
239 # browse subdirectories
239 # browse subdirectories
240 subdir = virtual + '/'
240 subdir = virtual + '/'
241 if [r for r in repos if r.startswith(subdir)]:
241 if [r for r in repos if r.startswith(subdir)]:
242 makeindex(req, subdir)
242 makeindex(req, subdir)
243 return
243 return
244
244
245 up = virtual.rfind('/')
245 up = virtual.rfind('/')
246 if up < 0:
246 if up < 0:
247 break
247 break
248 virtual = virtual[:up]
248 virtual = virtual[:up]
249
249
250 req.write(tmpl("notfound", repo=virtual))
250 req.write(tmpl("notfound", repo=virtual))
251 else:
251 else:
252 if req.form.has_key('static'):
252 if req.form.has_key('static'):
253 static = os.path.join(templater.templatepath(), "static")
253 static = os.path.join(templater.templatepath(), "static")
254 fname = req.form['static'][0]
254 fname = req.form['static'][0]
255 req.write(staticfile(static, fname, req)
255 req.write(staticfile(static, fname, req)
256 or tmpl("error", error="%r not found" % fname))
256 or tmpl("error", error="%r not found" % fname))
257 else:
257 else:
258 makeindex(req)
258 makeindex(req)
259 finally:
259 finally:
260 tmpl = None
260 tmpl = None
General Comments 0
You need to be logged in to leave comments. Login now