##// END OF EJS Templates
hgweb: move shortcut expansion to request instantiation
Dirkjan Ochtman -
r6774:0dbb56e9 default
parent child Browse files
Show More
@@ -1,379 +1,354 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 = {
20 'cl': [('cmd', ['changelog']), ('rev', None)],
21 'sl': [('cmd', ['shortlog']), ('rev', None)],
22 'cs': [('cmd', ['changeset']), ('node', None)],
23 'f': [('cmd', ['file']), ('filenode', None)],
24 'fl': [('cmd', ['filelog']), ('filenode', None)],
25 'fd': [('cmd', ['filediff']), ('node', None)],
26 'fa': [('cmd', ['annotate']), ('filenode', None)],
27 'mf': [('cmd', ['manifest']), ('manifest', None)],
28 'ca': [('cmd', ['archive']), ('node', None)],
29 'tags': [('cmd', ['tags'])],
30 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
31 'static': [('cmd', ['static']), ('file', None)]
32 }
33
34 class hgweb(object):
19 class hgweb(object):
35 def __init__(self, repo, name=None):
20 def __init__(self, repo, name=None):
36 if isinstance(repo, str):
21 if isinstance(repo, str):
37 parentui = ui.ui(report_untrusted=False, interactive=False)
22 parentui = ui.ui(report_untrusted=False, interactive=False)
38 self.repo = hg.repository(parentui, repo)
23 self.repo = hg.repository(parentui, repo)
39 else:
24 else:
40 self.repo = repo
25 self.repo = repo
41
26
42 hook.redirect(True)
27 hook.redirect(True)
43 self.mtime = -1
28 self.mtime = -1
44 self.reponame = name
29 self.reponame = name
45 self.archives = 'zip', 'gz', 'bz2'
30 self.archives = 'zip', 'gz', 'bz2'
46 self.stripecount = 1
31 self.stripecount = 1
47 self._capabilities = None
32 self._capabilities = None
48 # a repo owner may set web.templates in .hg/hgrc to get any file
33 # a repo owner may set web.templates in .hg/hgrc to get any file
49 # readable by the user running the CGI script
34 # readable by the user running the CGI script
50 self.templatepath = self.config("web", "templates",
35 self.templatepath = self.config("web", "templates",
51 templater.templatepath(),
36 templater.templatepath(),
52 untrusted=False)
37 untrusted=False)
53
38
54 # The CGI scripts are often run by a user different from the repo owner.
39 # 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.
40 # Trust the settings from the .hg/hgrc files by default.
56 def config(self, section, name, default=None, untrusted=True):
41 def config(self, section, name, default=None, untrusted=True):
57 return self.repo.ui.config(section, name, default,
42 return self.repo.ui.config(section, name, default,
58 untrusted=untrusted)
43 untrusted=untrusted)
59
44
60 def configbool(self, section, name, default=False, untrusted=True):
45 def configbool(self, section, name, default=False, untrusted=True):
61 return self.repo.ui.configbool(section, name, default,
46 return self.repo.ui.configbool(section, name, default,
62 untrusted=untrusted)
47 untrusted=untrusted)
63
48
64 def configlist(self, section, name, default=None, untrusted=True):
49 def configlist(self, section, name, default=None, untrusted=True):
65 return self.repo.ui.configlist(section, name, default,
50 return self.repo.ui.configlist(section, name, default,
66 untrusted=untrusted)
51 untrusted=untrusted)
67
52
68 def refresh(self):
53 def refresh(self):
69 mtime = get_mtime(self.repo.root)
54 mtime = get_mtime(self.repo.root)
70 if mtime != self.mtime:
55 if mtime != self.mtime:
71 self.mtime = mtime
56 self.mtime = mtime
72 self.repo = hg.repository(self.repo.ui, self.repo.root)
57 self.repo = hg.repository(self.repo.ui, self.repo.root)
73 self.maxchanges = int(self.config("web", "maxchanges", 10))
58 self.maxchanges = int(self.config("web", "maxchanges", 10))
74 self.stripecount = int(self.config("web", "stripes", 1))
59 self.stripecount = int(self.config("web", "stripes", 1))
75 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
60 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
76 self.maxfiles = int(self.config("web", "maxfiles", 10))
61 self.maxfiles = int(self.config("web", "maxfiles", 10))
77 self.allowpull = self.configbool("web", "allowpull", True)
62 self.allowpull = self.configbool("web", "allowpull", True)
78 self.encoding = self.config("web", "encoding", util._encoding)
63 self.encoding = self.config("web", "encoding", util._encoding)
79 self._capabilities = None
64 self._capabilities = None
80
65
81 def capabilities(self):
66 def capabilities(self):
82 if self._capabilities is not None:
67 if self._capabilities is not None:
83 return self._capabilities
68 return self._capabilities
84 caps = ['lookup', 'changegroupsubset']
69 caps = ['lookup', 'changegroupsubset']
85 if self.configbool('server', 'uncompressed'):
70 if self.configbool('server', 'uncompressed'):
86 caps.append('stream=%d' % self.repo.changelog.version)
71 caps.append('stream=%d' % self.repo.changelog.version)
87 if changegroup.bundlepriority:
72 if changegroup.bundlepriority:
88 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
73 caps.append('unbundle=%s' % ','.join(changegroup.bundlepriority))
89 self._capabilities = caps
74 self._capabilities = caps
90 return caps
75 return caps
91
76
92 def run(self):
77 def run(self):
93 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
78 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.")
79 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
95 import mercurial.hgweb.wsgicgi as wsgicgi
80 import mercurial.hgweb.wsgicgi as wsgicgi
96 wsgicgi.launch(self)
81 wsgicgi.launch(self)
97
82
98 def __call__(self, env, respond):
83 def __call__(self, env, respond):
99 req = wsgirequest(env, respond)
84 req = wsgirequest(env, respond)
100 self.run_wsgi(req)
85 self.run_wsgi(req)
101 return req
86 return req
102
87
103 def run_wsgi(self, req):
88 def run_wsgi(self, req):
104
89
105 self.refresh()
90 self.refresh()
106
91
107 # expand form shortcuts
108
109 for k in shortcuts.iterkeys():
110 if k in req.form:
111 for name, value in shortcuts[k]:
112 if value is None:
113 value = req.form[k]
114 req.form[name] = value
115 del req.form[k]
116
117 # work with CGI variables to create coherent structure
92 # work with CGI variables to create coherent structure
118 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
93 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
119
94
120 req.url = req.env['SCRIPT_NAME']
95 req.url = req.env['SCRIPT_NAME']
121 if not req.url.endswith('/'):
96 if not req.url.endswith('/'):
122 req.url += '/'
97 req.url += '/'
123 if 'REPO_NAME' in req.env:
98 if 'REPO_NAME' in req.env:
124 req.url += req.env['REPO_NAME'] + '/'
99 req.url += req.env['REPO_NAME'] + '/'
125
100
126 if 'PATH_INFO' in req.env:
101 if 'PATH_INFO' in req.env:
127 parts = req.env['PATH_INFO'].strip('/').split('/')
102 parts = req.env['PATH_INFO'].strip('/').split('/')
128 repo_parts = req.env.get('REPO_NAME', '').split('/')
103 repo_parts = req.env.get('REPO_NAME', '').split('/')
129 if parts[:len(repo_parts)] == repo_parts:
104 if parts[:len(repo_parts)] == repo_parts:
130 parts = parts[len(repo_parts):]
105 parts = parts[len(repo_parts):]
131 query = '/'.join(parts)
106 query = '/'.join(parts)
132 else:
107 else:
133 query = req.env['QUERY_STRING'].split('&', 1)[0]
108 query = req.env['QUERY_STRING'].split('&', 1)[0]
134 query = query.split(';', 1)[0]
109 query = query.split(';', 1)[0]
135
110
136 # translate user-visible url structure to internal structure
111 # translate user-visible url structure to internal structure
137
112
138 args = query.split('/', 2)
113 args = query.split('/', 2)
139 if 'cmd' not in req.form and args and args[0]:
114 if 'cmd' not in req.form and args and args[0]:
140
115
141 cmd = args.pop(0)
116 cmd = args.pop(0)
142 style = cmd.rfind('-')
117 style = cmd.rfind('-')
143 if style != -1:
118 if style != -1:
144 req.form['style'] = [cmd[:style]]
119 req.form['style'] = [cmd[:style]]
145 cmd = cmd[style+1:]
120 cmd = cmd[style+1:]
146
121
147 # avoid accepting e.g. style parameter as command
122 # avoid accepting e.g. style parameter as command
148 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
123 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
149 req.form['cmd'] = [cmd]
124 req.form['cmd'] = [cmd]
150
125
151 if args and args[0]:
126 if args and args[0]:
152 node = args.pop(0)
127 node = args.pop(0)
153 req.form['node'] = [node]
128 req.form['node'] = [node]
154 if args:
129 if args:
155 req.form['file'] = args
130 req.form['file'] = args
156
131
157 if cmd == 'static':
132 if cmd == 'static':
158 req.form['file'] = req.form['node']
133 req.form['file'] = req.form['node']
159 elif cmd == 'archive':
134 elif cmd == 'archive':
160 fn = req.form['node'][0]
135 fn = req.form['node'][0]
161 for type_, spec in self.archive_specs.iteritems():
136 for type_, spec in self.archive_specs.iteritems():
162 ext = spec[2]
137 ext = spec[2]
163 if fn.endswith(ext):
138 if fn.endswith(ext):
164 req.form['node'] = [fn[:-len(ext)]]
139 req.form['node'] = [fn[:-len(ext)]]
165 req.form['type'] = [type_]
140 req.form['type'] = [type_]
166
141
167 # process this if it's a protocol request
142 # process this if it's a protocol request
168
143
169 cmd = req.form.get('cmd', [''])[0]
144 cmd = req.form.get('cmd', [''])[0]
170 if cmd in protocol.__all__:
145 if cmd in protocol.__all__:
171 method = getattr(protocol, cmd)
146 method = getattr(protocol, cmd)
172 method(self, req)
147 method(self, req)
173 return
148 return
174
149
175 # process the web interface request
150 # process the web interface request
176
151
177 try:
152 try:
178
153
179 tmpl = self.templater(req)
154 tmpl = self.templater(req)
180 ctype = tmpl('mimetype', encoding=self.encoding)
155 ctype = tmpl('mimetype', encoding=self.encoding)
181 ctype = templater.stringify(ctype)
156 ctype = templater.stringify(ctype)
182
157
183 if cmd == '':
158 if cmd == '':
184 req.form['cmd'] = [tmpl.cache['default']]
159 req.form['cmd'] = [tmpl.cache['default']]
185 cmd = req.form['cmd'][0]
160 cmd = req.form['cmd'][0]
186
161
187 if cmd not in webcommands.__all__:
162 if cmd not in webcommands.__all__:
188 msg = 'no such method: %s' % cmd
163 msg = 'no such method: %s' % cmd
189 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
164 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
190 elif cmd == 'file' and 'raw' in req.form.get('style', []):
165 elif cmd == 'file' and 'raw' in req.form.get('style', []):
191 self.ctype = ctype
166 self.ctype = ctype
192 content = webcommands.rawfile(self, req, tmpl)
167 content = webcommands.rawfile(self, req, tmpl)
193 else:
168 else:
194 content = getattr(webcommands, cmd)(self, req, tmpl)
169 content = getattr(webcommands, cmd)(self, req, tmpl)
195 req.respond(HTTP_OK, ctype)
170 req.respond(HTTP_OK, ctype)
196
171
197 req.write(content)
172 req.write(content)
198 del tmpl
173 del tmpl
199
174
200 except revlog.LookupError, err:
175 except revlog.LookupError, err:
201 req.respond(HTTP_NOT_FOUND, ctype)
176 req.respond(HTTP_NOT_FOUND, ctype)
202 msg = str(err)
177 msg = str(err)
203 if 'manifest' not in msg:
178 if 'manifest' not in msg:
204 msg = 'revision not found: %s' % err.name
179 msg = 'revision not found: %s' % err.name
205 req.write(tmpl('error', error=msg))
180 req.write(tmpl('error', error=msg))
206 except (RepoError, revlog.RevlogError), inst:
181 except (RepoError, revlog.RevlogError), inst:
207 req.respond(HTTP_SERVER_ERROR, ctype)
182 req.respond(HTTP_SERVER_ERROR, ctype)
208 req.write(tmpl('error', error=str(inst)))
183 req.write(tmpl('error', error=str(inst)))
209 except ErrorResponse, inst:
184 except ErrorResponse, inst:
210 req.respond(inst.code, ctype)
185 req.respond(inst.code, ctype)
211 req.write(tmpl('error', error=inst.message))
186 req.write(tmpl('error', error=inst.message))
212
187
213 def templater(self, req):
188 def templater(self, req):
214
189
215 # determine scheme, port and server name
190 # determine scheme, port and server name
216 # this is needed to create absolute urls
191 # this is needed to create absolute urls
217
192
218 proto = req.env.get('wsgi.url_scheme')
193 proto = req.env.get('wsgi.url_scheme')
219 if proto == 'https':
194 if proto == 'https':
220 proto = 'https'
195 proto = 'https'
221 default_port = "443"
196 default_port = "443"
222 else:
197 else:
223 proto = 'http'
198 proto = 'http'
224 default_port = "80"
199 default_port = "80"
225
200
226 port = req.env["SERVER_PORT"]
201 port = req.env["SERVER_PORT"]
227 port = port != default_port and (":" + port) or ""
202 port = port != default_port and (":" + port) or ""
228 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
203 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
229 staticurl = self.config("web", "staticurl") or req.url + 'static/'
204 staticurl = self.config("web", "staticurl") or req.url + 'static/'
230 if not staticurl.endswith('/'):
205 if not staticurl.endswith('/'):
231 staticurl += '/'
206 staticurl += '/'
232
207
233 # some functions for the templater
208 # some functions for the templater
234
209
235 def header(**map):
210 def header(**map):
236 yield tmpl('header', encoding=self.encoding, **map)
211 yield tmpl('header', encoding=self.encoding, **map)
237
212
238 def footer(**map):
213 def footer(**map):
239 yield tmpl("footer", **map)
214 yield tmpl("footer", **map)
240
215
241 def motd(**map):
216 def motd(**map):
242 yield self.config("web", "motd", "")
217 yield self.config("web", "motd", "")
243
218
244 def sessionvars(**map):
219 def sessionvars(**map):
245 fields = []
220 fields = []
246 if 'style' in req.form:
221 if 'style' in req.form:
247 style = req.form['style'][0]
222 style = req.form['style'][0]
248 if style != self.config('web', 'style', ''):
223 if style != self.config('web', 'style', ''):
249 fields.append(('style', style))
224 fields.append(('style', style))
250
225
251 separator = req.url[-1] == '?' and ';' or '?'
226 separator = req.url[-1] == '?' and ';' or '?'
252 for name, value in fields:
227 for name, value in fields:
253 yield dict(name=name, value=value, separator=separator)
228 yield dict(name=name, value=value, separator=separator)
254 separator = ';'
229 separator = ';'
255
230
256 # figure out which style to use
231 # figure out which style to use
257
232
258 style = self.config("web", "style", "")
233 style = self.config("web", "style", "")
259 if 'style' in req.form:
234 if 'style' in req.form:
260 style = req.form['style'][0]
235 style = req.form['style'][0]
261 mapfile = style_map(self.templatepath, style)
236 mapfile = style_map(self.templatepath, style)
262
237
263 if not self.reponame:
238 if not self.reponame:
264 self.reponame = (self.config("web", "name")
239 self.reponame = (self.config("web", "name")
265 or req.env.get('REPO_NAME')
240 or req.env.get('REPO_NAME')
266 or req.url.strip('/') or self.repo.root)
241 or req.url.strip('/') or self.repo.root)
267
242
268 # create the templater
243 # create the templater
269
244
270 tmpl = templater.templater(mapfile, templatefilters.filters,
245 tmpl = templater.templater(mapfile, templatefilters.filters,
271 defaults={"url": req.url,
246 defaults={"url": req.url,
272 "staticurl": staticurl,
247 "staticurl": staticurl,
273 "urlbase": urlbase,
248 "urlbase": urlbase,
274 "repo": self.reponame,
249 "repo": self.reponame,
275 "header": header,
250 "header": header,
276 "footer": footer,
251 "footer": footer,
277 "motd": motd,
252 "motd": motd,
278 "sessionvars": sessionvars
253 "sessionvars": sessionvars
279 })
254 })
280 return tmpl
255 return tmpl
281
256
282 def archivelist(self, nodeid):
257 def archivelist(self, nodeid):
283 allowed = self.configlist("web", "allow_archive")
258 allowed = self.configlist("web", "allow_archive")
284 for i, spec in self.archive_specs.iteritems():
259 for i, spec in self.archive_specs.iteritems():
285 if i in allowed or self.configbool("web", "allow" + i):
260 if i in allowed or self.configbool("web", "allow" + i):
286 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
261 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
287
262
288 def listfilediffs(self, tmpl, files, changeset):
263 def listfilediffs(self, tmpl, files, changeset):
289 for f in files[:self.maxfiles]:
264 for f in files[:self.maxfiles]:
290 yield tmpl("filedifflink", node=hex(changeset), file=f)
265 yield tmpl("filedifflink", node=hex(changeset), file=f)
291 if len(files) > self.maxfiles:
266 if len(files) > self.maxfiles:
292 yield tmpl("fileellipses")
267 yield tmpl("fileellipses")
293
268
294 def diff(self, tmpl, node1, node2, files):
269 def diff(self, tmpl, node1, node2, files):
295 def filterfiles(filters, files):
270 def filterfiles(filters, files):
296 l = [x for x in files if x in filters]
271 l = [x for x in files if x in filters]
297
272
298 for t in filters:
273 for t in filters:
299 if t and t[-1] != os.sep:
274 if t and t[-1] != os.sep:
300 t += os.sep
275 t += os.sep
301 l += [x for x in files if x.startswith(t)]
276 l += [x for x in files if x.startswith(t)]
302 return l
277 return l
303
278
304 parity = paritygen(self.stripecount)
279 parity = paritygen(self.stripecount)
305 def diffblock(diff, f, fn):
280 def diffblock(diff, f, fn):
306 yield tmpl("diffblock",
281 yield tmpl("diffblock",
307 lines=prettyprintlines(diff),
282 lines=prettyprintlines(diff),
308 parity=parity.next(),
283 parity=parity.next(),
309 file=f,
284 file=f,
310 filenode=hex(fn or nullid))
285 filenode=hex(fn or nullid))
311
286
312 blockcount = countgen()
287 blockcount = countgen()
313 def prettyprintlines(diff):
288 def prettyprintlines(diff):
314 blockno = blockcount.next()
289 blockno = blockcount.next()
315 for lineno, l in enumerate(diff.splitlines(1)):
290 for lineno, l in enumerate(diff.splitlines(1)):
316 if blockno == 0:
291 if blockno == 0:
317 lineno = lineno + 1
292 lineno = lineno + 1
318 else:
293 else:
319 lineno = "%d.%d" % (blockno, lineno + 1)
294 lineno = "%d.%d" % (blockno, lineno + 1)
320 if l.startswith('+'):
295 if l.startswith('+'):
321 ltype = "difflineplus"
296 ltype = "difflineplus"
322 elif l.startswith('-'):
297 elif l.startswith('-'):
323 ltype = "difflineminus"
298 ltype = "difflineminus"
324 elif l.startswith('@'):
299 elif l.startswith('@'):
325 ltype = "difflineat"
300 ltype = "difflineat"
326 else:
301 else:
327 ltype = "diffline"
302 ltype = "diffline"
328 yield tmpl(ltype,
303 yield tmpl(ltype,
329 line=l,
304 line=l,
330 lineid="l%s" % lineno,
305 lineid="l%s" % lineno,
331 linenumber="% 8s" % lineno)
306 linenumber="% 8s" % lineno)
332
307
333 r = self.repo
308 r = self.repo
334 c1 = r.changectx(node1)
309 c1 = r.changectx(node1)
335 c2 = r.changectx(node2)
310 c2 = r.changectx(node2)
336 date1 = util.datestr(c1.date())
311 date1 = util.datestr(c1.date())
337 date2 = util.datestr(c2.date())
312 date2 = util.datestr(c2.date())
338
313
339 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
314 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
340 if files:
315 if files:
341 modified, added, removed = map(lambda x: filterfiles(files, x),
316 modified, added, removed = map(lambda x: filterfiles(files, x),
342 (modified, added, removed))
317 (modified, added, removed))
343
318
344 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
319 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
345 for f in modified:
320 for f in modified:
346 to = c1.filectx(f).data()
321 to = c1.filectx(f).data()
347 tn = c2.filectx(f).data()
322 tn = c2.filectx(f).data()
348 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
323 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
349 opts=diffopts), f, tn)
324 opts=diffopts), f, tn)
350 for f in added:
325 for f in added:
351 to = None
326 to = None
352 tn = c2.filectx(f).data()
327 tn = c2.filectx(f).data()
353 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
328 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
354 opts=diffopts), f, tn)
329 opts=diffopts), f, tn)
355 for f in removed:
330 for f in removed:
356 to = c1.filectx(f).data()
331 to = c1.filectx(f).data()
357 tn = None
332 tn = None
358 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
333 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
359 opts=diffopts), f, tn)
334 opts=diffopts), f, tn)
360
335
361 archive_specs = {
336 archive_specs = {
362 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
337 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
363 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
338 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
364 'zip': ('application/zip', 'zip', '.zip', None),
339 'zip': ('application/zip', 'zip', '.zip', None),
365 }
340 }
366
341
367 def check_perm(self, req, op, default):
342 def check_perm(self, req, op, default):
368 '''check permission for operation based on user auth.
343 '''check permission for operation based on user auth.
369 return true if op allowed, else false.
344 return true if op allowed, else false.
370 default is policy to use if no config given.'''
345 default is policy to use if no config given.'''
371
346
372 user = req.env.get('REMOTE_USER')
347 user = req.env.get('REMOTE_USER')
373
348
374 deny = self.configlist('web', 'deny_' + op)
349 deny = self.configlist('web', 'deny_' + op)
375 if deny and (not user or deny == ['*'] or user in deny):
350 if deny and (not user or deny == ['*'] or user in deny):
376 return False
351 return False
377
352
378 allow = self.configlist('web', 'allow_' + op)
353 allow = self.configlist('web', 'allow_' + op)
379 return (allow and (allow == ['*'] or user in allow)) or default
354 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,101 +1,126 b''
1 # hgweb/request.py - An http request from either CGI or the standalone server.
1 # hgweb/request.py - An http request from either CGI or the standalone server.
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 socket, cgi, errno
9 import socket, cgi, errno
10 from common import ErrorResponse, statusmessage
10 from common import ErrorResponse, statusmessage
11
11
12 shortcuts = {
13 'cl': [('cmd', ['changelog']), ('rev', None)],
14 'sl': [('cmd', ['shortlog']), ('rev', None)],
15 'cs': [('cmd', ['changeset']), ('node', None)],
16 'f': [('cmd', ['file']), ('filenode', None)],
17 'fl': [('cmd', ['filelog']), ('filenode', None)],
18 'fd': [('cmd', ['filediff']), ('node', None)],
19 'fa': [('cmd', ['annotate']), ('filenode', None)],
20 'mf': [('cmd', ['manifest']), ('manifest', None)],
21 'ca': [('cmd', ['archive']), ('node', None)],
22 'tags': [('cmd', ['tags'])],
23 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
24 'static': [('cmd', ['static']), ('file', None)]
25 }
26
27 def expand(form):
28 for k in shortcuts.iterkeys():
29 if k in form:
30 for name, value in shortcuts[k]:
31 if value is None:
32 value = form[k]
33 form[name] = value
34 del form[k]
35 return form
36
12 class wsgirequest(object):
37 class wsgirequest(object):
13 def __init__(self, wsgienv, start_response):
38 def __init__(self, wsgienv, start_response):
14 version = wsgienv['wsgi.version']
39 version = wsgienv['wsgi.version']
15 if (version < (1, 0)) or (version >= (2, 0)):
40 if (version < (1, 0)) or (version >= (2, 0)):
16 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
41 raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
17 % version)
42 % version)
18 self.inp = wsgienv['wsgi.input']
43 self.inp = wsgienv['wsgi.input']
19 self.err = wsgienv['wsgi.errors']
44 self.err = wsgienv['wsgi.errors']
20 self.threaded = wsgienv['wsgi.multithread']
45 self.threaded = wsgienv['wsgi.multithread']
21 self.multiprocess = wsgienv['wsgi.multiprocess']
46 self.multiprocess = wsgienv['wsgi.multiprocess']
22 self.run_once = wsgienv['wsgi.run_once']
47 self.run_once = wsgienv['wsgi.run_once']
23 self.env = wsgienv
48 self.env = wsgienv
24 self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
49 self.form = expand(cgi.parse(self.inp, self.env, keep_blank_values=1))
25 self._start_response = start_response
50 self._start_response = start_response
26 self.server_write = None
51 self.server_write = None
27 self.headers = []
52 self.headers = []
28
53
29 def __iter__(self):
54 def __iter__(self):
30 return iter([])
55 return iter([])
31
56
32 def read(self, count=-1):
57 def read(self, count=-1):
33 return self.inp.read(count)
58 return self.inp.read(count)
34
59
35 def respond(self, status, type=None, filename=None, length=0):
60 def respond(self, status, type=None, filename=None, length=0):
36 if self._start_response is not None:
61 if self._start_response is not None:
37
62
38 self.httphdr(type, filename, length)
63 self.httphdr(type, filename, length)
39 if not self.headers:
64 if not self.headers:
40 raise RuntimeError("request.write called before headers sent")
65 raise RuntimeError("request.write called before headers sent")
41
66
42 for k, v in self.headers:
67 for k, v in self.headers:
43 if not isinstance(v, str):
68 if not isinstance(v, str):
44 raise TypeError('header value must be string: %r' % v)
69 raise TypeError('header value must be string: %r' % v)
45
70
46 if isinstance(status, ErrorResponse):
71 if isinstance(status, ErrorResponse):
47 status = statusmessage(status.code)
72 status = statusmessage(status.code)
48 elif status == 200:
73 elif status == 200:
49 status = '200 Script output follows'
74 status = '200 Script output follows'
50 elif isinstance(status, int):
75 elif isinstance(status, int):
51 status = statusmessage(status)
76 status = statusmessage(status)
52
77
53 self.server_write = self._start_response(status, self.headers)
78 self.server_write = self._start_response(status, self.headers)
54 self._start_response = None
79 self._start_response = None
55 self.headers = []
80 self.headers = []
56
81
57 def write(self, thing):
82 def write(self, thing):
58 if hasattr(thing, "__iter__"):
83 if hasattr(thing, "__iter__"):
59 for part in thing:
84 for part in thing:
60 self.write(part)
85 self.write(part)
61 else:
86 else:
62 thing = str(thing)
87 thing = str(thing)
63 try:
88 try:
64 self.server_write(thing)
89 self.server_write(thing)
65 except socket.error, inst:
90 except socket.error, inst:
66 if inst[0] != errno.ECONNRESET:
91 if inst[0] != errno.ECONNRESET:
67 raise
92 raise
68
93
69 def writelines(self, lines):
94 def writelines(self, lines):
70 for line in lines:
95 for line in lines:
71 self.write(line)
96 self.write(line)
72
97
73 def flush(self):
98 def flush(self):
74 return None
99 return None
75
100
76 def close(self):
101 def close(self):
77 return None
102 return None
78
103
79 def header(self, headers=[('Content-Type','text/html')]):
104 def header(self, headers=[('Content-Type','text/html')]):
80 self.headers.extend(headers)
105 self.headers.extend(headers)
81
106
82 def httphdr(self, type=None, filename=None, length=0, headers={}):
107 def httphdr(self, type=None, filename=None, length=0, headers={}):
83 headers = headers.items()
108 headers = headers.items()
84 if type is not None:
109 if type is not None:
85 headers.append(('Content-Type', type))
110 headers.append(('Content-Type', type))
86 if filename:
111 if filename:
87 filename = (filename.split('/')[-1]
112 filename = (filename.split('/')[-1]
88 .replace('\\', '\\\\').replace('"', '\\"'))
113 .replace('\\', '\\\\').replace('"', '\\"'))
89 headers.append(('Content-Disposition',
114 headers.append(('Content-Disposition',
90 'inline; filename="%s"' % filename))
115 'inline; filename="%s"' % filename))
91 if length:
116 if length:
92 headers.append(('Content-Length', str(length)))
117 headers.append(('Content-Length', str(length)))
93 self.header(headers)
118 self.header(headers)
94
119
95 def wsgiapplication(app_maker):
120 def wsgiapplication(app_maker):
96 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
121 '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
97 can and should now be used as a WSGI application.'''
122 can and should now be used as a WSGI application.'''
98 application = app_maker()
123 application = app_maker()
99 def run_wsgi(env, respond):
124 def run_wsgi(env, respond):
100 return application(env, respond)
125 return application(env, respond)
101 return run_wsgi
126 return run_wsgi
General Comments 0
You need to be logged in to leave comments. Login now