##// END OF EJS Templates
Merge from crew-stable.
Dirkjan Ochtman -
r6460:a63aed91 merge default
parent child Browse files
Show More
@@ -0,0 +1,59 b''
1 #!/bin/sh
2 # This tests if hgweb and hgwebdir still work if the REQUEST_URI variable is
3 # no longer passed with the request. Instead, SCRIPT_NAME and PATH_INFO
4 # should be used from d74fc8dec2b4 onward to route the request.
5
6 mkdir repo
7 cd repo
8 hg init
9 echo foo > bar
10 hg add bar
11 hg commit -m "test" -d "0 0" -u "Testing"
12 hg tip
13
14 cat > request.py <<EOF
15 from mercurial.hgweb import hgweb, hgwebdir
16 from StringIO import StringIO
17 import os, sys
18
19 errors = StringIO()
20 input = StringIO()
21
22 def startrsp(headers, data):
23 print '---- HEADERS'
24 print headers
25 print '---- DATA'
26 print data
27 return output.write
28
29 env = {
30 'wsgi.version': (1, 0),
31 'wsgi.url_scheme': 'http',
32 'wsgi.errors': errors,
33 'wsgi.input': input,
34 'wsgi.multithread': False,
35 'wsgi.multiprocess': False,
36 'wsgi.run_once': False,
37 'REQUEST_METHOD': 'GET',
38 'SCRIPT_NAME': '',
39 'SERVER_NAME': '127.0.0.1',
40 'SERVER_PORT': os.environ['HGPORT'],
41 'SERVER_PROTOCOL': 'HTTP/1.0'
42 }
43
44 output = StringIO()
45 env['QUERY_STRING'] = 'style=atom'
46 hgweb('.', name = 'repo')(env, startrsp)
47 print output.getvalue()
48 print '---- ERRORS'
49 print errors.getvalue()
50
51 output = StringIO()
52 env['QUERY_STRING'] = 'style=raw'
53 hgwebdir({'repo': '.'})(env, startrsp)
54 print output.getvalue()
55 print '---- ERRORS'
56 print errors.getvalue()
57 EOF
58
59 python request.py | sed "s/http:\/\/127\.0\.0\.1:[0-9]*\//http:\/\/127.0.0.1\//"
@@ -0,0 +1,50 b''
1 changeset: 0:4cbec7e6f8c4
2 tag: tip
3 user: Testing
4 date: Thu Jan 01 00:00:00 1970 +0000
5 summary: test
6
7 ---- HEADERS
8 200 Script output follows
9 ---- DATA
10 [('Content-Type', 'application/atom+xml; charset=ascii')]
11 <?xml version="1.0" encoding="ascii"?>
12 <feed xmlns="http://www.w3.org/2005/Atom">
13 <!-- Changelog -->
14 <id>http://127.0.0.1/</id>
15 <link rel="self" href="http://127.0.0.1/atom-log"/>
16 <link rel="alternate" href="http://127.0.0.1/"/>
17 <title>repo Changelog</title>
18 <updated>1970-01-01T00:00:00+00:00</updated>
19
20 <entry>
21 <title>test</title>
22 <id>http://www.selenic.com/mercurial/#changeset-4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e</id>
23 <link href="http://127.0.0.1/rev/4cbec7e6f8c42eb52b6b52670e1f7560ae9a101e"/>
24 <author>
25 <name>Testing</name>
26 <email>&#84;&#101;&#115;&#116;&#105;&#110;&#103;</email>
27 </author>
28 <updated>1970-01-01T00:00:00+00:00</updated>
29 <published>1970-01-01T00:00:00+00:00</published>
30 <content type="xhtml">
31 <div xmlns="http://www.w3.org/1999/xhtml">
32 <pre xml:space="preserve">test</pre>
33 </div>
34 </content>
35 </entry>
36
37 </feed>
38
39 ---- ERRORS
40
41 ---- HEADERS
42 200 Script output follows
43 ---- DATA
44 [('Content-Type', 'text/plain; charset=ascii')]
45
46 repo/
47
48
49 ---- ERRORS
50
@@ -1,379 +1,379 b''
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
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, mimetypes
9 import os, mimetypes
10 from mercurial.node import hex, nullid
10 from mercurial.node import hex, nullid
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import mdiff, ui, hg, util, patch, hook
12 from mercurial import mdiff, ui, hg, util, patch, hook
13 from mercurial import revlog, templater, templatefilters, changegroup
13 from mercurial import revlog, templater, templatefilters, changegroup
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol, webutil
17 import webcommands, protocol, webutil
18
18
19 shortcuts = {
19 shortcuts = {
20 'cl': [('cmd', ['changelog']), ('rev', None)],
20 'cl': [('cmd', ['changelog']), ('rev', None)],
21 'sl': [('cmd', ['shortlog']), ('rev', None)],
21 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 'cs': [('cmd', ['changeset']), ('node', None)],
22 'cs': [('cmd', ['changeset']), ('node', None)],
23 'f': [('cmd', ['file']), ('filenode', None)],
23 'f': [('cmd', ['file']), ('filenode', None)],
24 'fl': [('cmd', ['filelog']), ('filenode', None)],
24 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 'fd': [('cmd', ['filediff']), ('node', None)],
25 'fd': [('cmd', ['filediff']), ('node', None)],
26 'fa': [('cmd', ['annotate']), ('filenode', None)],
26 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 'mf': [('cmd', ['manifest']), ('manifest', None)],
27 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 'ca': [('cmd', ['archive']), ('node', None)],
28 'ca': [('cmd', ['archive']), ('node', None)],
29 'tags': [('cmd', ['tags'])],
29 'tags': [('cmd', ['tags'])],
30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 'static': [('cmd', ['static']), ('file', None)]
31 'static': [('cmd', ['static']), ('file', None)]
32 }
32 }
33
33
34 class hgweb(object):
34 class hgweb(object):
35 def __init__(self, repo, name=None):
35 def __init__(self, repo, name=None):
36 if isinstance(repo, str):
36 if isinstance(repo, str):
37 parentui = ui.ui(report_untrusted=False, interactive=False)
37 parentui = ui.ui(report_untrusted=False, interactive=False)
38 self.repo = hg.repository(parentui, repo)
38 self.repo = hg.repository(parentui, repo)
39 else:
39 else:
40 self.repo = repo
40 self.repo = repo
41
41
42 hook.redirect(True)
42 hook.redirect(True)
43 self.mtime = -1
43 self.mtime = -1
44 self.reponame = name
44 self.reponame = name
45 self.archives = 'zip', 'gz', 'bz2'
45 self.archives = 'zip', 'gz', 'bz2'
46 self.stripecount = 1
46 self.stripecount = 1
47 self._capabilities = None
47 self._capabilities = None
48 # a repo owner may set web.templates in .hg/hgrc to get any file
48 # a repo owner may set web.templates in .hg/hgrc to get any file
49 # readable by the user running the CGI script
49 # readable by the user running the CGI script
50 self.templatepath = self.config("web", "templates",
50 self.templatepath = self.config("web", "templates",
51 templater.templatepath(),
51 templater.templatepath(),
52 untrusted=False)
52 untrusted=False)
53
53
54 # The CGI scripts are often run by a user different from the repo owner.
54 # The CGI scripts are often run by a user different from the repo owner.
55 # Trust the settings from the .hg/hgrc files by default.
55 # Trust the settings from the .hg/hgrc files by default.
56 def config(self, section, name, default=None, untrusted=True):
56 def config(self, section, name, default=None, untrusted=True):
57 return self.repo.ui.config(section, name, default,
57 return self.repo.ui.config(section, name, default,
58 untrusted=untrusted)
58 untrusted=untrusted)
59
59
60 def configbool(self, section, name, default=False, untrusted=True):
60 def configbool(self, section, name, default=False, untrusted=True):
61 return self.repo.ui.configbool(section, name, default,
61 return self.repo.ui.configbool(section, name, default,
62 untrusted=untrusted)
62 untrusted=untrusted)
63
63
64 def configlist(self, section, name, default=None, untrusted=True):
64 def configlist(self, section, name, default=None, untrusted=True):
65 return self.repo.ui.configlist(section, name, default,
65 return self.repo.ui.configlist(section, name, default,
66 untrusted=untrusted)
66 untrusted=untrusted)
67
67
68 def refresh(self):
68 def refresh(self):
69 mtime = get_mtime(self.repo.root)
69 mtime = get_mtime(self.repo.root)
70 if mtime != self.mtime:
70 if mtime != self.mtime:
71 self.mtime = mtime
71 self.mtime = mtime
72 self.repo = hg.repository(self.repo.ui, self.repo.root)
72 self.repo = hg.repository(self.repo.ui, self.repo.root)
73 self.maxchanges = int(self.config("web", "maxchanges", 10))
73 self.maxchanges = int(self.config("web", "maxchanges", 10))
74 self.stripecount = int(self.config("web", "stripes", 1))
74 self.stripecount = int(self.config("web", "stripes", 1))
75 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
75 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
76 self.maxfiles = int(self.config("web", "maxfiles", 10))
76 self.maxfiles = int(self.config("web", "maxfiles", 10))
77 self.allowpull = self.configbool("web", "allowpull", True)
77 self.allowpull = self.configbool("web", "allowpull", True)
78 self.encoding = self.config("web", "encoding", util._encoding)
78 self.encoding = self.config("web", "encoding", util._encoding)
79 self._capabilities = None
79 self._capabilities = None
80
80
81 def capabilities(self):
81 def capabilities(self):
82 if self._capabilities is not None:
82 if self._capabilities is not None:
83 return self._capabilities
83 return self._capabilities
84 caps = ['lookup', 'changegroupsubset']
84 caps = ['lookup', 'changegroupsubset']
85 if self.configbool('server', 'uncompressed'):
85 if self.configbool('server', 'uncompressed'):
86 caps.append('stream=%d' % self.repo.changelog.version)
86 caps.append('stream=%d' % self.repo.changelog.version)
87 if changegroup.bundlepriority:
87 if changegroup.bundlepriority:
88 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
88 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
89 self._capabilities = caps
89 self._capabilities = caps
90 return caps
90 return caps
91
91
92 def run(self):
92 def run(self):
93 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
93 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
94 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
94 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
95 import mercurial.hgweb.wsgicgi as wsgicgi
95 import mercurial.hgweb.wsgicgi as wsgicgi
96 wsgicgi.launch(self)
96 wsgicgi.launch(self)
97
97
98 def __call__(self, env, respond):
98 def __call__(self, env, respond):
99 req = wsgirequest(env, respond)
99 req = wsgirequest(env, respond)
100 self.run_wsgi(req)
100 self.run_wsgi(req)
101 return req
101 return req
102
102
103 def run_wsgi(self, req):
103 def run_wsgi(self, req):
104
104
105 self.refresh()
105 self.refresh()
106
106
107 # expand form shortcuts
107 # expand form shortcuts
108
108
109 for k in shortcuts.iterkeys():
109 for k in shortcuts.iterkeys():
110 if k in req.form:
110 if k in req.form:
111 for name, value in shortcuts[k]:
111 for name, value in shortcuts[k]:
112 if value is None:
112 if value is None:
113 value = req.form[k]
113 value = req.form[k]
114 req.form[name] = value
114 req.form[name] = value
115 del req.form[k]
115 del req.form[k]
116
116
117 # work with CGI variables to create coherent structure
117 # work with CGI variables to create coherent structure
118 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
118 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
119
119
120 req.url = req.env['SCRIPT_NAME']
120 req.url = req.env['SCRIPT_NAME']
121 if not req.url.endswith('/'):
121 if not req.url.endswith('/'):
122 req.url += '/'
122 req.url += '/'
123 if 'REPO_NAME' in req.env:
123 if 'REPO_NAME' in req.env:
124 req.url += req.env['REPO_NAME'] + '/'
124 req.url += req.env['REPO_NAME'] + '/'
125
125
126 if req.env.get('PATH_INFO'):
126 if 'PATH_INFO' in req.env:
127 parts = req.env.get('PATH_INFO').strip('/').split('/')
127 parts = req.env['PATH_INFO'].strip('/').split('/')
128 repo_parts = req.env.get('REPO_NAME', '').split('/')
128 repo_parts = req.env.get('REPO_NAME', '').split('/')
129 if parts[:len(repo_parts)] == repo_parts:
129 if parts[:len(repo_parts)] == repo_parts:
130 parts = parts[len(repo_parts):]
130 parts = parts[len(repo_parts):]
131 query = '/'.join(parts)
131 query = '/'.join(parts)
132 else:
132 else:
133 query = req.env['QUERY_STRING'].split('&', 1)[0]
133 query = req.env['QUERY_STRING'].split('&', 1)[0]
134 query = query.split(';', 1)[0]
134 query = query.split(';', 1)[0]
135
135
136 # translate user-visible url structure to internal structure
136 # translate user-visible url structure to internal structure
137
137
138 args = query.split('/', 2)
138 args = query.split('/', 2)
139 if 'cmd' not in req.form and args and args[0]:
139 if 'cmd' not in req.form and args and args[0]:
140
140
141 cmd = args.pop(0)
141 cmd = args.pop(0)
142 style = cmd.rfind('-')
142 style = cmd.rfind('-')
143 if style != -1:
143 if style != -1:
144 req.form['style'] = [cmd[:style]]
144 req.form['style'] = [cmd[:style]]
145 cmd = cmd[style+1:]
145 cmd = cmd[style+1:]
146
146
147 # avoid accepting e.g. style parameter as command
147 # avoid accepting e.g. style parameter as command
148 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
148 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
149 req.form['cmd'] = [cmd]
149 req.form['cmd'] = [cmd]
150
150
151 if args and args[0]:
151 if args and args[0]:
152 node = args.pop(0)
152 node = args.pop(0)
153 req.form['node'] = [node]
153 req.form['node'] = [node]
154 if args:
154 if args:
155 req.form['file'] = args
155 req.form['file'] = args
156
156
157 if cmd == 'static':
157 if cmd == 'static':
158 req.form['file'] = req.form['node']
158 req.form['file'] = req.form['node']
159 elif cmd == 'archive':
159 elif cmd == 'archive':
160 fn = req.form['node'][0]
160 fn = req.form['node'][0]
161 for type_, spec in self.archive_specs.iteritems():
161 for type_, spec in self.archive_specs.iteritems():
162 ext = spec[2]
162 ext = spec[2]
163 if fn.endswith(ext):
163 if fn.endswith(ext):
164 req.form['node'] = [fn[:-len(ext)]]
164 req.form['node'] = [fn[:-len(ext)]]
165 req.form['type'] = [type_]
165 req.form['type'] = [type_]
166
166
167 # process this if it's a protocol request
167 # process this if it's a protocol request
168
168
169 cmd = req.form.get('cmd', [''])[0]
169 cmd = req.form.get('cmd', [''])[0]
170 if cmd in protocol.__all__:
170 if cmd in protocol.__all__:
171 method = getattr(protocol, cmd)
171 method = getattr(protocol, cmd)
172 method(self, req)
172 method(self, req)
173 return
173 return
174
174
175 # process the web interface request
175 # process the web interface request
176
176
177 try:
177 try:
178
178
179 tmpl = self.templater(req)
179 tmpl = self.templater(req)
180 ctype = tmpl('mimetype', encoding=self.encoding)
180 ctype = tmpl('mimetype', encoding=self.encoding)
181 ctype = templater.stringify(ctype)
181 ctype = templater.stringify(ctype)
182
182
183 if cmd == '':
183 if cmd == '':
184 req.form['cmd'] = [tmpl.cache['default']]
184 req.form['cmd'] = [tmpl.cache['default']]
185 cmd = req.form['cmd'][0]
185 cmd = req.form['cmd'][0]
186
186
187 if cmd not in webcommands.__all__:
187 if cmd not in webcommands.__all__:
188 msg = 'no such method: %s' % cmd
188 msg = 'no such method: %s' % cmd
189 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
189 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
190 elif cmd == 'file' and 'raw' in req.form.get('style', []):
190 elif cmd == 'file' and 'raw' in req.form.get('style', []):
191 self.ctype = ctype
191 self.ctype = ctype
192 content = webcommands.rawfile(self, req, tmpl)
192 content = webcommands.rawfile(self, req, tmpl)
193 else:
193 else:
194 content = getattr(webcommands, cmd)(self, req, tmpl)
194 content = getattr(webcommands, cmd)(self, req, tmpl)
195 req.respond(HTTP_OK, ctype)
195 req.respond(HTTP_OK, ctype)
196
196
197 req.write(content)
197 req.write(content)
198 del tmpl
198 del tmpl
199
199
200 except revlog.LookupError, err:
200 except revlog.LookupError, err:
201 req.respond(HTTP_NOT_FOUND, ctype)
201 req.respond(HTTP_NOT_FOUND, ctype)
202 msg = str(err)
202 msg = str(err)
203 if 'manifest' not in msg:
203 if 'manifest' not in msg:
204 msg = 'revision not found: %s' % err.name
204 msg = 'revision not found: %s' % err.name
205 req.write(tmpl('error', error=msg))
205 req.write(tmpl('error', error=msg))
206 except (RepoError, revlog.RevlogError), inst:
206 except (RepoError, revlog.RevlogError), inst:
207 req.respond(HTTP_SERVER_ERROR, ctype)
207 req.respond(HTTP_SERVER_ERROR, ctype)
208 req.write(tmpl('error', error=str(inst)))
208 req.write(tmpl('error', error=str(inst)))
209 except ErrorResponse, inst:
209 except ErrorResponse, inst:
210 req.respond(inst.code, ctype)
210 req.respond(inst.code, ctype)
211 req.write(tmpl('error', error=inst.message))
211 req.write(tmpl('error', error=inst.message))
212
212
213 def templater(self, req):
213 def templater(self, req):
214
214
215 # determine scheme, port and server name
215 # determine scheme, port and server name
216 # this is needed to create absolute urls
216 # this is needed to create absolute urls
217
217
218 proto = req.env.get('wsgi.url_scheme')
218 proto = req.env.get('wsgi.url_scheme')
219 if proto == 'https':
219 if proto == 'https':
220 proto = 'https'
220 proto = 'https'
221 default_port = "443"
221 default_port = "443"
222 else:
222 else:
223 proto = 'http'
223 proto = 'http'
224 default_port = "80"
224 default_port = "80"
225
225
226 port = req.env["SERVER_PORT"]
226 port = req.env["SERVER_PORT"]
227 port = port != default_port and (":" + port) or ""
227 port = port != default_port and (":" + port) or ""
228 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
228 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
229 staticurl = self.config("web", "staticurl") or req.url + 'static/'
229 staticurl = self.config("web", "staticurl") or req.url + 'static/'
230 if not staticurl.endswith('/'):
230 if not staticurl.endswith('/'):
231 staticurl += '/'
231 staticurl += '/'
232
232
233 # some functions for the templater
233 # some functions for the templater
234
234
235 def header(**map):
235 def header(**map):
236 yield tmpl('header', encoding=self.encoding, **map)
236 yield tmpl('header', encoding=self.encoding, **map)
237
237
238 def footer(**map):
238 def footer(**map):
239 yield tmpl("footer", **map)
239 yield tmpl("footer", **map)
240
240
241 def motd(**map):
241 def motd(**map):
242 yield self.config("web", "motd", "")
242 yield self.config("web", "motd", "")
243
243
244 def sessionvars(**map):
244 def sessionvars(**map):
245 fields = []
245 fields = []
246 if 'style' in req.form:
246 if 'style' in req.form:
247 style = req.form['style'][0]
247 style = req.form['style'][0]
248 if style != self.config('web', 'style', ''):
248 if style != self.config('web', 'style', ''):
249 fields.append(('style', style))
249 fields.append(('style', style))
250
250
251 separator = req.url[-1] == '?' and ';' or '?'
251 separator = req.url[-1] == '?' and ';' or '?'
252 for name, value in fields:
252 for name, value in fields:
253 yield dict(name=name, value=value, separator=separator)
253 yield dict(name=name, value=value, separator=separator)
254 separator = ';'
254 separator = ';'
255
255
256 # figure out which style to use
256 # figure out which style to use
257
257
258 style = self.config("web", "style", "")
258 style = self.config("web", "style", "")
259 if 'style' in req.form:
259 if 'style' in req.form:
260 style = req.form['style'][0]
260 style = req.form['style'][0]
261 mapfile = style_map(self.templatepath, style)
261 mapfile = style_map(self.templatepath, style)
262
262
263 if not self.reponame:
263 if not self.reponame:
264 self.reponame = (self.config("web", "name")
264 self.reponame = (self.config("web", "name")
265 or req.env.get('REPO_NAME')
265 or req.env.get('REPO_NAME')
266 or req.url.strip('/') or self.repo.root)
266 or req.url.strip('/') or self.repo.root)
267
267
268 # create the templater
268 # create the templater
269
269
270 tmpl = templater.templater(mapfile, templatefilters.filters,
270 tmpl = templater.templater(mapfile, templatefilters.filters,
271 defaults={"url": req.url,
271 defaults={"url": req.url,
272 "staticurl": staticurl,
272 "staticurl": staticurl,
273 "urlbase": urlbase,
273 "urlbase": urlbase,
274 "repo": self.reponame,
274 "repo": self.reponame,
275 "header": header,
275 "header": header,
276 "footer": footer,
276 "footer": footer,
277 "motd": motd,
277 "motd": motd,
278 "sessionvars": sessionvars
278 "sessionvars": sessionvars
279 })
279 })
280 return tmpl
280 return tmpl
281
281
282 def archivelist(self, nodeid):
282 def archivelist(self, nodeid):
283 allowed = self.configlist("web", "allow_archive")
283 allowed = self.configlist("web", "allow_archive")
284 for i, spec in self.archive_specs.iteritems():
284 for i, spec in self.archive_specs.iteritems():
285 if i in allowed or self.configbool("web", "allow" + i):
285 if i in allowed or self.configbool("web", "allow" + i):
286 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
286 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
287
287
288 def listfilediffs(self, tmpl, files, changeset):
288 def listfilediffs(self, tmpl, files, changeset):
289 for f in files[:self.maxfiles]:
289 for f in files[:self.maxfiles]:
290 yield tmpl("filedifflink", node=hex(changeset), file=f)
290 yield tmpl("filedifflink", node=hex(changeset), file=f)
291 if len(files) > self.maxfiles:
291 if len(files) > self.maxfiles:
292 yield tmpl("fileellipses")
292 yield tmpl("fileellipses")
293
293
294 def diff(self, tmpl, node1, node2, files):
294 def diff(self, tmpl, node1, node2, files):
295 def filterfiles(filters, files):
295 def filterfiles(filters, files):
296 l = [x for x in files if x in filters]
296 l = [x for x in files if x in filters]
297
297
298 for t in filters:
298 for t in filters:
299 if t and t[-1] != os.sep:
299 if t and t[-1] != os.sep:
300 t += os.sep
300 t += os.sep
301 l += [x for x in files if x.startswith(t)]
301 l += [x for x in files if x.startswith(t)]
302 return l
302 return l
303
303
304 parity = paritygen(self.stripecount)
304 parity = paritygen(self.stripecount)
305 def diffblock(diff, f, fn):
305 def diffblock(diff, f, fn):
306 yield tmpl("diffblock",
306 yield tmpl("diffblock",
307 lines=prettyprintlines(diff),
307 lines=prettyprintlines(diff),
308 parity=parity.next(),
308 parity=parity.next(),
309 file=f,
309 file=f,
310 filenode=hex(fn or nullid))
310 filenode=hex(fn or nullid))
311
311
312 blockcount = countgen()
312 blockcount = countgen()
313 def prettyprintlines(diff):
313 def prettyprintlines(diff):
314 blockno = blockcount.next()
314 blockno = blockcount.next()
315 for lineno, l in enumerate(diff.splitlines(1)):
315 for lineno, l in enumerate(diff.splitlines(1)):
316 if blockno == 0:
316 if blockno == 0:
317 lineno = lineno + 1
317 lineno = lineno + 1
318 else:
318 else:
319 lineno = "%d.%d" % (blockno, lineno + 1)
319 lineno = "%d.%d" % (blockno, lineno + 1)
320 if l.startswith('+'):
320 if l.startswith('+'):
321 ltype = "difflineplus"
321 ltype = "difflineplus"
322 elif l.startswith('-'):
322 elif l.startswith('-'):
323 ltype = "difflineminus"
323 ltype = "difflineminus"
324 elif l.startswith('@'):
324 elif l.startswith('@'):
325 ltype = "difflineat"
325 ltype = "difflineat"
326 else:
326 else:
327 ltype = "diffline"
327 ltype = "diffline"
328 yield tmpl(ltype,
328 yield tmpl(ltype,
329 line=l,
329 line=l,
330 lineid="l%s" % lineno,
330 lineid="l%s" % lineno,
331 linenumber="% 8s" % lineno)
331 linenumber="% 8s" % lineno)
332
332
333 r = self.repo
333 r = self.repo
334 c1 = r.changectx(node1)
334 c1 = r.changectx(node1)
335 c2 = r.changectx(node2)
335 c2 = r.changectx(node2)
336 date1 = util.datestr(c1.date())
336 date1 = util.datestr(c1.date())
337 date2 = util.datestr(c2.date())
337 date2 = util.datestr(c2.date())
338
338
339 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
339 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
340 if files:
340 if files:
341 modified, added, removed = map(lambda x: filterfiles(files, x),
341 modified, added, removed = map(lambda x: filterfiles(files, x),
342 (modified, added, removed))
342 (modified, added, removed))
343
343
344 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
344 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
345 for f in modified:
345 for f in modified:
346 to = c1.filectx(f).data()
346 to = c1.filectx(f).data()
347 tn = c2.filectx(f).data()
347 tn = c2.filectx(f).data()
348 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
348 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
349 opts=diffopts), f, tn)
349 opts=diffopts), f, tn)
350 for f in added:
350 for f in added:
351 to = None
351 to = None
352 tn = c2.filectx(f).data()
352 tn = c2.filectx(f).data()
353 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
353 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
354 opts=diffopts), f, tn)
354 opts=diffopts), f, tn)
355 for f in removed:
355 for f in removed:
356 to = c1.filectx(f).data()
356 to = c1.filectx(f).data()
357 tn = None
357 tn = None
358 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
358 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
359 opts=diffopts), f, tn)
359 opts=diffopts), f, tn)
360
360
361 archive_specs = {
361 archive_specs = {
362 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
362 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
363 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
363 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
364 'zip': ('application/zip', 'zip', '.zip', None),
364 'zip': ('application/zip', 'zip', '.zip', None),
365 }
365 }
366
366
367 def check_perm(self, req, op, default):
367 def check_perm(self, req, op, default):
368 '''check permission for operation based on user auth.
368 '''check permission for operation based on user auth.
369 return true if op allowed, else false.
369 return true if op allowed, else false.
370 default is policy to use if no config given.'''
370 default is policy to use if no config given.'''
371
371
372 user = req.env.get('REMOTE_USER')
372 user = req.env.get('REMOTE_USER')
373
373
374 deny = self.configlist('web', 'deny_' + op)
374 deny = self.configlist('web', 'deny_' + op)
375 if deny and (not user or deny == ['*'] or user in deny):
375 if deny and (not user or deny == ['*'] or user in deny):
376 return False
376 return False
377
377
378 allow = self.configlist('web', 'allow_' + op)
378 allow = self.configlist('web', 'allow_' + op)
379 return (allow and (allow == ['*'] or user in allow)) or default
379 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,288 +1,290 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
9 import os
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial.repo import RepoError
11 from mercurial.repo import RepoError
12 from mercurial import ui, hg, util, templater, templatefilters
12 from mercurial import ui, hg, util, templater, templatefilters
13 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
13 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen,\
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
14 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from hgweb_mod import hgweb
15 from hgweb_mod import hgweb
16 from request import wsgirequest
16 from request import wsgirequest
17
17
18 # This is a stopgap
18 # This is a stopgap
19 class hgwebdir(object):
19 class hgwebdir(object):
20 def __init__(self, config, parentui=None):
20 def __init__(self, config, parentui=None):
21 def cleannames(items):
21 def cleannames(items):
22 return [(util.pconvert(name).strip('/'), path)
22 return [(util.pconvert(name).strip('/'), path)
23 for name, path in items]
23 for name, path in items]
24
24
25 self.parentui = parentui or ui.ui(report_untrusted=False,
25 self.parentui = parentui or ui.ui(report_untrusted=False,
26 interactive = False)
26 interactive = False)
27 self.motd = None
27 self.motd = None
28 self.style = None
28 self.style = None
29 self.stripecount = None
29 self.stripecount = None
30 self.repos_sorted = ('name', False)
30 self.repos_sorted = ('name', False)
31 self._baseurl = None
31 self._baseurl = None
32 if isinstance(config, (list, tuple)):
32 if isinstance(config, (list, tuple)):
33 self.repos = cleannames(config)
33 self.repos = cleannames(config)
34 self.repos_sorted = ('', False)
34 self.repos_sorted = ('', False)
35 elif isinstance(config, dict):
35 elif isinstance(config, dict):
36 self.repos = cleannames(config.items())
36 self.repos = cleannames(config.items())
37 self.repos.sort()
37 self.repos.sort()
38 else:
38 else:
39 if isinstance(config, util.configparser):
39 if isinstance(config, util.configparser):
40 cp = config
40 cp = config
41 else:
41 else:
42 cp = util.configparser()
42 cp = util.configparser()
43 cp.read(config)
43 cp.read(config)
44 self.repos = []
44 self.repos = []
45 if cp.has_section('web'):
45 if cp.has_section('web'):
46 if cp.has_option('web', 'motd'):
46 if cp.has_option('web', 'motd'):
47 self.motd = cp.get('web', 'motd')
47 self.motd = cp.get('web', 'motd')
48 if cp.has_option('web', 'style'):
48 if cp.has_option('web', 'style'):
49 self.style = cp.get('web', 'style')
49 self.style = cp.get('web', 'style')
50 if cp.has_option('web', 'stripes'):
50 if cp.has_option('web', 'stripes'):
51 self.stripecount = int(cp.get('web', 'stripes'))
51 self.stripecount = int(cp.get('web', 'stripes'))
52 if cp.has_option('web', 'baseurl'):
52 if cp.has_option('web', 'baseurl'):
53 self._baseurl = cp.get('web', 'baseurl')
53 self._baseurl = cp.get('web', 'baseurl')
54 if cp.has_section('paths'):
54 if cp.has_section('paths'):
55 self.repos.extend(cleannames(cp.items('paths')))
55 self.repos.extend(cleannames(cp.items('paths')))
56 if cp.has_section('collections'):
56 if cp.has_section('collections'):
57 for prefix, root in cp.items('collections'):
57 for prefix, root in cp.items('collections'):
58 for path in util.walkrepos(root, followsym=True):
58 for path in util.walkrepos(root, followsym=True):
59 repo = os.path.normpath(path)
59 repo = os.path.normpath(path)
60 name = repo
60 name = repo
61 if name.startswith(prefix):
61 if name.startswith(prefix):
62 name = name[len(prefix):]
62 name = name[len(prefix):]
63 self.repos.append((name.lstrip(os.sep), repo))
63 self.repos.append((name.lstrip(os.sep), repo))
64 self.repos.sort()
64 self.repos.sort()
65
65
66 def run(self):
66 def run(self):
67 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
67 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
68 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
68 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
69 import mercurial.hgweb.wsgicgi as wsgicgi
69 import mercurial.hgweb.wsgicgi as wsgicgi
70 wsgicgi.launch(self)
70 wsgicgi.launch(self)
71
71
72 def __call__(self, env, respond):
72 def __call__(self, env, respond):
73 req = wsgirequest(env, respond)
73 req = wsgirequest(env, respond)
74 self.run_wsgi(req)
74 self.run_wsgi(req)
75 return req
75 return req
76
76
77 def run_wsgi(self, req):
77 def run_wsgi(self, req):
78
78
79 try:
79 try:
80 try:
80 try:
81
81
82 virtual = req.env.get("PATH_INFO", "").strip('/')
82 virtual = req.env.get("PATH_INFO", "").strip('/')
83 tmpl = self.templater(req)
83 tmpl = self.templater(req)
84 ctype = tmpl('mimetype', encoding=util._encoding)
84 ctype = tmpl('mimetype', encoding=util._encoding)
85 ctype = templater.stringify(ctype)
85 ctype = templater.stringify(ctype)
86
86
87 # a static file
87 # a static file
88 if virtual.startswith('static/') or 'static' in req.form:
88 if virtual.startswith('static/') or 'static' in req.form:
89 static = os.path.join(templater.templatepath(), 'static')
89 static = os.path.join(templater.templatepath(), 'static')
90 if virtual.startswith('static/'):
90 if virtual.startswith('static/'):
91 fname = virtual[7:]
91 fname = virtual[7:]
92 else:
92 else:
93 fname = req.form['static'][0]
93 fname = req.form['static'][0]
94 req.write(staticfile(static, fname, req))
94 req.write(staticfile(static, fname, req))
95 return
95 return
96
96
97 # top-level index
97 # top-level index
98 elif not virtual:
98 elif not virtual:
99 req.respond(HTTP_OK, ctype)
99 req.respond(HTTP_OK, ctype)
100 req.write(self.makeindex(req, tmpl))
100 req.write(self.makeindex(req, tmpl))
101 return
101 return
102
102
103 # nested indexes and hgwebs
103 # nested indexes and hgwebs
104
104
105 repos = dict(self.repos)
105 repos = dict(self.repos)
106 while virtual:
106 while virtual:
107 real = repos.get(virtual)
107 real = repos.get(virtual)
108 if real:
108 if real:
109 req.env['REPO_NAME'] = virtual
109 req.env['REPO_NAME'] = virtual
110 try:
110 try:
111 repo = hg.repository(self.parentui, real)
111 repo = hg.repository(self.parentui, real)
112 hgweb(repo).run_wsgi(req)
112 hgweb(repo).run_wsgi(req)
113 return
113 return
114 except IOError, inst:
114 except IOError, inst:
115 msg = inst.strerror
115 msg = inst.strerror
116 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
116 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
117 except RepoError, inst:
117 except RepoError, inst:
118 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
118 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
119
119
120 # browse subdirectories
120 # browse subdirectories
121 subdir = virtual + '/'
121 subdir = virtual + '/'
122 if [r for r in repos if r.startswith(subdir)]:
122 if [r for r in repos if r.startswith(subdir)]:
123 req.respond(HTTP_OK, ctype)
123 req.respond(HTTP_OK, ctype)
124 req.write(self.makeindex(req, tmpl, subdir))
124 req.write(self.makeindex(req, tmpl, subdir))
125 return
125 return
126
126
127 up = virtual.rfind('/')
127 up = virtual.rfind('/')
128 if up < 0:
128 if up < 0:
129 break
129 break
130 virtual = virtual[:up]
130 virtual = virtual[:up]
131
131
132 # prefixes not found
132 # prefixes not found
133 req.respond(HTTP_NOT_FOUND, ctype)
133 req.respond(HTTP_NOT_FOUND, ctype)
134 req.write(tmpl("notfound", repo=virtual))
134 req.write(tmpl("notfound", repo=virtual))
135
135
136 except ErrorResponse, err:
136 except ErrorResponse, err:
137 req.respond(err.code, ctype)
137 req.respond(err.code, ctype)
138 req.write(tmpl('error', error=err.message or ''))
138 req.write(tmpl('error', error=err.message or ''))
139 finally:
139 finally:
140 tmpl = None
140 tmpl = None
141
141
142 def makeindex(self, req, tmpl, subdir=""):
142 def makeindex(self, req, tmpl, subdir=""):
143
143
144 def archivelist(ui, nodeid, url):
144 def archivelist(ui, nodeid, url):
145 allowed = ui.configlist("web", "allow_archive", untrusted=True)
145 allowed = ui.configlist("web", "allow_archive", untrusted=True)
146 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
146 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
147 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
147 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
148 untrusted=True):
148 untrusted=True):
149 yield {"type" : i[0], "extension": i[1],
149 yield {"type" : i[0], "extension": i[1],
150 "node": nodeid, "url": url}
150 "node": nodeid, "url": url}
151
151
152 def entries(sortcolumn="", descending=False, subdir="", **map):
152 def entries(sortcolumn="", descending=False, subdir="", **map):
153 def sessionvars(**map):
153 def sessionvars(**map):
154 fields = []
154 fields = []
155 if 'style' in req.form:
155 if 'style' in req.form:
156 style = req.form['style'][0]
156 style = req.form['style'][0]
157 if style != get('web', 'style', ''):
157 if style != get('web', 'style', ''):
158 fields.append(('style', style))
158 fields.append(('style', style))
159
159
160 separator = url[-1] == '?' and ';' or '?'
160 separator = url[-1] == '?' and ';' or '?'
161 for name, value in fields:
161 for name, value in fields:
162 yield dict(name=name, value=value, separator=separator)
162 yield dict(name=name, value=value, separator=separator)
163 separator = ';'
163 separator = ';'
164
164
165 rows = []
165 rows = []
166 parity = paritygen(self.stripecount)
166 parity = paritygen(self.stripecount)
167 for name, path in self.repos:
167 for name, path in self.repos:
168 if not name.startswith(subdir):
168 if not name.startswith(subdir):
169 continue
169 continue
170 name = name[len(subdir):]
170 name = name[len(subdir):]
171
171
172 u = ui.ui(parentui=self.parentui)
172 u = ui.ui(parentui=self.parentui)
173 try:
173 try:
174 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
174 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
175 except Exception, e:
175 except Exception, e:
176 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
176 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
177 continue
177 continue
178 def get(section, name, default=None):
178 def get(section, name, default=None):
179 return u.config(section, name, default, untrusted=True)
179 return u.config(section, name, default, untrusted=True)
180
180
181 if u.configbool("web", "hidden", untrusted=True):
181 if u.configbool("web", "hidden", untrusted=True):
182 continue
182 continue
183
183
184 parts = [req.env['PATH_INFO'].rstrip('/'), name]
184 parts = [name]
185 if 'PATH_INFO' in req.env:
186 parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
185 if req.env['SCRIPT_NAME']:
187 if req.env['SCRIPT_NAME']:
186 parts.insert(0, req.env['SCRIPT_NAME'])
188 parts.insert(0, req.env['SCRIPT_NAME'])
187 url = ('/'.join(parts).replace("//", "/")) + '/'
189 url = ('/'.join(parts).replace("//", "/")) + '/'
188
190
189 # update time with local timezone
191 # update time with local timezone
190 try:
192 try:
191 d = (get_mtime(path), util.makedate()[1])
193 d = (get_mtime(path), util.makedate()[1])
192 except OSError:
194 except OSError:
193 continue
195 continue
194
196
195 contact = get_contact(get)
197 contact = get_contact(get)
196 description = get("web", "description", "")
198 description = get("web", "description", "")
197 name = get("web", "name", name)
199 name = get("web", "name", name)
198 row = dict(contact=contact or "unknown",
200 row = dict(contact=contact or "unknown",
199 contact_sort=contact.upper() or "unknown",
201 contact_sort=contact.upper() or "unknown",
200 name=name,
202 name=name,
201 name_sort=name,
203 name_sort=name,
202 url=url,
204 url=url,
203 description=description or "unknown",
205 description=description or "unknown",
204 description_sort=description.upper() or "unknown",
206 description_sort=description.upper() or "unknown",
205 lastchange=d,
207 lastchange=d,
206 lastchange_sort=d[1]-d[0],
208 lastchange_sort=d[1]-d[0],
207 sessionvars=sessionvars,
209 sessionvars=sessionvars,
208 archives=archivelist(u, "tip", url))
210 archives=archivelist(u, "tip", url))
209 if (not sortcolumn
211 if (not sortcolumn
210 or (sortcolumn, descending) == self.repos_sorted):
212 or (sortcolumn, descending) == self.repos_sorted):
211 # fast path for unsorted output
213 # fast path for unsorted output
212 row['parity'] = parity.next()
214 row['parity'] = parity.next()
213 yield row
215 yield row
214 else:
216 else:
215 rows.append((row["%s_sort" % sortcolumn], row))
217 rows.append((row["%s_sort" % sortcolumn], row))
216 if rows:
218 if rows:
217 rows.sort()
219 rows.sort()
218 if descending:
220 if descending:
219 rows.reverse()
221 rows.reverse()
220 for key, row in rows:
222 for key, row in rows:
221 row['parity'] = parity.next()
223 row['parity'] = parity.next()
222 yield row
224 yield row
223
225
224 sortable = ["name", "description", "contact", "lastchange"]
226 sortable = ["name", "description", "contact", "lastchange"]
225 sortcolumn, descending = self.repos_sorted
227 sortcolumn, descending = self.repos_sorted
226 if 'sort' in req.form:
228 if 'sort' in req.form:
227 sortcolumn = req.form['sort'][0]
229 sortcolumn = req.form['sort'][0]
228 descending = sortcolumn.startswith('-')
230 descending = sortcolumn.startswith('-')
229 if descending:
231 if descending:
230 sortcolumn = sortcolumn[1:]
232 sortcolumn = sortcolumn[1:]
231 if sortcolumn not in sortable:
233 if sortcolumn not in sortable:
232 sortcolumn = ""
234 sortcolumn = ""
233
235
234 sort = [("sort_%s" % column,
236 sort = [("sort_%s" % column,
235 "%s%s" % ((not descending and column == sortcolumn)
237 "%s%s" % ((not descending and column == sortcolumn)
236 and "-" or "", column))
238 and "-" or "", column))
237 for column in sortable]
239 for column in sortable]
238
240
239 if self._baseurl is not None:
241 if self._baseurl is not None:
240 req.env['SCRIPT_NAME'] = self._baseurl
242 req.env['SCRIPT_NAME'] = self._baseurl
241
243
242 return tmpl("index", entries=entries, subdir=subdir,
244 return tmpl("index", entries=entries, subdir=subdir,
243 sortcolumn=sortcolumn, descending=descending,
245 sortcolumn=sortcolumn, descending=descending,
244 **dict(sort))
246 **dict(sort))
245
247
246 def templater(self, req):
248 def templater(self, req):
247
249
248 def header(**map):
250 def header(**map):
249 yield tmpl('header', encoding=util._encoding, **map)
251 yield tmpl('header', encoding=util._encoding, **map)
250
252
251 def footer(**map):
253 def footer(**map):
252 yield tmpl("footer", **map)
254 yield tmpl("footer", **map)
253
255
254 def motd(**map):
256 def motd(**map):
255 if self.motd is not None:
257 if self.motd is not None:
256 yield self.motd
258 yield self.motd
257 else:
259 else:
258 yield config('web', 'motd', '')
260 yield config('web', 'motd', '')
259
261
260 def config(section, name, default=None, untrusted=True):
262 def config(section, name, default=None, untrusted=True):
261 return self.parentui.config(section, name, default, untrusted)
263 return self.parentui.config(section, name, default, untrusted)
262
264
263 if self._baseurl is not None:
265 if self._baseurl is not None:
264 req.env['SCRIPT_NAME'] = self._baseurl
266 req.env['SCRIPT_NAME'] = self._baseurl
265
267
266 url = req.env.get('SCRIPT_NAME', '')
268 url = req.env.get('SCRIPT_NAME', '')
267 if not url.endswith('/'):
269 if not url.endswith('/'):
268 url += '/'
270 url += '/'
269
271
270 staticurl = config('web', 'staticurl') or url + 'static/'
272 staticurl = config('web', 'staticurl') or url + 'static/'
271 if not staticurl.endswith('/'):
273 if not staticurl.endswith('/'):
272 staticurl += '/'
274 staticurl += '/'
273
275
274 style = self.style
276 style = self.style
275 if style is None:
277 if style is None:
276 style = config('web', 'style', '')
278 style = config('web', 'style', '')
277 if 'style' in req.form:
279 if 'style' in req.form:
278 style = req.form['style'][0]
280 style = req.form['style'][0]
279 if self.stripecount is None:
281 if self.stripecount is None:
280 self.stripecount = int(config('web', 'stripes', 1))
282 self.stripecount = int(config('web', 'stripes', 1))
281 mapfile = style_map(templater.templatepath(), style)
283 mapfile = style_map(templater.templatepath(), style)
282 tmpl = templater.templater(mapfile, templatefilters.filters,
284 tmpl = templater.templater(mapfile, templatefilters.filters,
283 defaults={"header": header,
285 defaults={"header": header,
284 "footer": footer,
286 "footer": footer,
285 "motd": motd,
287 "motd": motd,
286 "url": url,
288 "url": url,
287 "staticurl": staticurl})
289 "staticurl": staticurl})
288 return tmpl
290 return tmpl
General Comments 0
You need to be logged in to leave comments. Login now