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