##// END OF EJS Templates
Use SCRIPT_NAME and PATH_INFO instead of REQUEST_URI. This is required by WSGI (fixes issue846).
Dirkjan Ochtman -
r5579:e15f7db0 default
parent child Browse files
Show More
@@ -0,0 +1,74 b''
1 #!/bin/sh
2
3 mkdir repo
4 cd repo
5 hg init
6 echo foo > bar
7 hg add bar
8 hg commit -m "test" -d "0 0" -u "Testing"
9 hg tip
10
11 cat > request.py <<EOF
12 from mercurial.hgweb import hgweb, hgwebdir
13 from StringIO import StringIO
14 import os, sys
15
16 errors = StringIO()
17 input = StringIO()
18
19 def startrsp(headers, data):
20 print '---- HEADERS'
21 print headers
22 print '---- DATA'
23 print data
24 return output.write
25
26 env = {
27 'wsgi.version': (1, 0),
28 'wsgi.url_scheme': 'http',
29 'wsgi.errors': errors,
30 'wsgi.input': input,
31 'wsgi.multithread': False,
32 'wsgi.multiprocess': False,
33 'wsgi.run_once': False,
34 'REQUEST_METHOD': 'GET',
35 'SCRIPT_NAME': '',
36 'SERVER_NAME': '127.0.0.1',
37 'SERVER_PORT': os.environ['HGPORT'],
38 'SERVER_PROTOCOL': 'HTTP/1.0'
39 }
40
41 output = StringIO()
42 env['PATH_INFO'] = '/'
43 env['QUERY_STRING'] = 'style=atom'
44 hgweb('.', name = 'repo')(env, startrsp)
45 print output.getvalue()
46 print '---- ERRORS'
47 print errors.getvalue()
48
49 output = StringIO()
50 env['PATH_INFO'] = '/file/tip/'
51 env['QUERY_STRING'] = 'style=raw'
52 hgweb('.', name = 'repo')(env, startrsp)
53 print output.getvalue()
54 print '---- ERRORS'
55 print errors.getvalue()
56
57 output = StringIO()
58 env['PATH_INFO'] = '/'
59 env['QUERY_STRING'] = 'style=raw'
60 hgwebdir({'repo': '.'})(env, startrsp)
61 print output.getvalue()
62 print '---- ERRORS'
63 print errors.getvalue()
64
65 output = StringIO()
66 env['PATH_INFO'] = '/repo/file/tip/'
67 env['QUERY_STRING'] = 'style=raw'
68 hgwebdir({'repo': '.'})(env, startrsp)
69 print output.getvalue()
70 print '---- ERRORS'
71 print errors.getvalue()
72 EOF
73
74 python request.py | sed "s/http:\/\/127\.0\.0\.1:[0-9]*\//http:\/\/127.0.0.1\//"
@@ -0,0 +1,72 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 -rw-r--r-- 4 bar
47
48
49
50 ---- ERRORS
51
52 ---- HEADERS
53 200 Script output follows
54 ---- DATA
55 [('content-type', 'text/plain; charset=ascii')]
56
57 /repo/
58
59
60 ---- ERRORS
61
62 ---- HEADERS
63 200 Script output follows
64 ---- DATA
65 [('content-type', 'text/plain; charset=ascii')]
66
67 -rw-r--r-- 4 bar
68
69
70
71 ---- ERRORS
72
@@ -1,1224 +1,1208 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 errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
9 import errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
10 import tempfile, urllib, bz2
10 import tempfile, urllib, bz2
11 from mercurial.node import *
11 from mercurial.node import *
12 from mercurial.i18n import gettext as _
12 from mercurial.i18n import gettext as _
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
14 from mercurial import revlog, templater
14 from mercurial import revlog, templater
15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
16 from request import wsgirequest
16 from request import wsgirequest
17
17
18 def _up(p):
18 def _up(p):
19 if p[0] != "/":
19 if p[0] != "/":
20 p = "/" + p
20 p = "/" + p
21 if p[-1] == "/":
21 if p[-1] == "/":
22 p = p[:-1]
22 p = p[:-1]
23 up = os.path.dirname(p)
23 up = os.path.dirname(p)
24 if up == "/":
24 if up == "/":
25 return "/"
25 return "/"
26 return up + "/"
26 return up + "/"
27
27
28 def revnavgen(pos, pagelen, limit, nodefunc):
28 def revnavgen(pos, pagelen, limit, nodefunc):
29 def seq(factor, limit=None):
29 def seq(factor, limit=None):
30 if limit:
30 if limit:
31 yield limit
31 yield limit
32 if limit >= 20 and limit <= 40:
32 if limit >= 20 and limit <= 40:
33 yield 50
33 yield 50
34 else:
34 else:
35 yield 1 * factor
35 yield 1 * factor
36 yield 3 * factor
36 yield 3 * factor
37 for f in seq(factor * 10):
37 for f in seq(factor * 10):
38 yield f
38 yield f
39
39
40 def nav(**map):
40 def nav(**map):
41 l = []
41 l = []
42 last = 0
42 last = 0
43 for f in seq(1, pagelen):
43 for f in seq(1, pagelen):
44 if f < pagelen or f <= last:
44 if f < pagelen or f <= last:
45 continue
45 continue
46 if f > limit:
46 if f > limit:
47 break
47 break
48 last = f
48 last = f
49 if pos + f < limit:
49 if pos + f < limit:
50 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
50 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
51 if pos - f >= 0:
51 if pos - f >= 0:
52 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
52 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
53
53
54 try:
54 try:
55 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
55 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
56
56
57 for label, node in l:
57 for label, node in l:
58 yield {"label": label, "node": node}
58 yield {"label": label, "node": node}
59
59
60 yield {"label": "tip", "node": "tip"}
60 yield {"label": "tip", "node": "tip"}
61 except hg.RepoError:
61 except hg.RepoError:
62 pass
62 pass
63
63
64 return nav
64 return nav
65
65
66 class hgweb(object):
66 class hgweb(object):
67 def __init__(self, repo, name=None):
67 def __init__(self, repo, name=None):
68 if isinstance(repo, str):
68 if isinstance(repo, str):
69 parentui = ui.ui(report_untrusted=False, interactive=False)
69 parentui = ui.ui(report_untrusted=False, interactive=False)
70 self.repo = hg.repository(parentui, repo)
70 self.repo = hg.repository(parentui, repo)
71 else:
71 else:
72 self.repo = repo
72 self.repo = repo
73
73
74 self.mtime = -1
74 self.mtime = -1
75 self.reponame = name
75 self.reponame = name
76 self.archives = 'zip', 'gz', 'bz2'
76 self.archives = 'zip', 'gz', 'bz2'
77 self.stripecount = 1
77 self.stripecount = 1
78 # a repo owner may set web.templates in .hg/hgrc to get any file
78 # a repo owner may set web.templates in .hg/hgrc to get any file
79 # readable by the user running the CGI script
79 # readable by the user running the CGI script
80 self.templatepath = self.config("web", "templates",
80 self.templatepath = self.config("web", "templates",
81 templater.templatepath(),
81 templater.templatepath(),
82 untrusted=False)
82 untrusted=False)
83
83
84 # The CGI scripts are often run by a user different from the repo owner.
84 # The CGI scripts are often run by a user different from the repo owner.
85 # Trust the settings from the .hg/hgrc files by default.
85 # Trust the settings from the .hg/hgrc files by default.
86 def config(self, section, name, default=None, untrusted=True):
86 def config(self, section, name, default=None, untrusted=True):
87 return self.repo.ui.config(section, name, default,
87 return self.repo.ui.config(section, name, default,
88 untrusted=untrusted)
88 untrusted=untrusted)
89
89
90 def configbool(self, section, name, default=False, untrusted=True):
90 def configbool(self, section, name, default=False, untrusted=True):
91 return self.repo.ui.configbool(section, name, default,
91 return self.repo.ui.configbool(section, name, default,
92 untrusted=untrusted)
92 untrusted=untrusted)
93
93
94 def configlist(self, section, name, default=None, untrusted=True):
94 def configlist(self, section, name, default=None, untrusted=True):
95 return self.repo.ui.configlist(section, name, default,
95 return self.repo.ui.configlist(section, name, default,
96 untrusted=untrusted)
96 untrusted=untrusted)
97
97
98 def refresh(self):
98 def refresh(self):
99 mtime = get_mtime(self.repo.root)
99 mtime = get_mtime(self.repo.root)
100 if mtime != self.mtime:
100 if mtime != self.mtime:
101 self.mtime = mtime
101 self.mtime = mtime
102 self.repo = hg.repository(self.repo.ui, self.repo.root)
102 self.repo = hg.repository(self.repo.ui, self.repo.root)
103 self.maxchanges = int(self.config("web", "maxchanges", 10))
103 self.maxchanges = int(self.config("web", "maxchanges", 10))
104 self.stripecount = int(self.config("web", "stripes", 1))
104 self.stripecount = int(self.config("web", "stripes", 1))
105 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
105 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
106 self.maxfiles = int(self.config("web", "maxfiles", 10))
106 self.maxfiles = int(self.config("web", "maxfiles", 10))
107 self.allowpull = self.configbool("web", "allowpull", True)
107 self.allowpull = self.configbool("web", "allowpull", True)
108 self.encoding = self.config("web", "encoding", util._encoding)
108 self.encoding = self.config("web", "encoding", util._encoding)
109
109
110 def archivelist(self, nodeid):
110 def archivelist(self, nodeid):
111 allowed = self.configlist("web", "allow_archive")
111 allowed = self.configlist("web", "allow_archive")
112 for i, spec in self.archive_specs.iteritems():
112 for i, spec in self.archive_specs.iteritems():
113 if i in allowed or self.configbool("web", "allow" + i):
113 if i in allowed or self.configbool("web", "allow" + i):
114 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
114 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
115
115
116 def listfilediffs(self, files, changeset):
116 def listfilediffs(self, files, changeset):
117 for f in files[:self.maxfiles]:
117 for f in files[:self.maxfiles]:
118 yield self.t("filedifflink", node=hex(changeset), file=f)
118 yield self.t("filedifflink", node=hex(changeset), file=f)
119 if len(files) > self.maxfiles:
119 if len(files) > self.maxfiles:
120 yield self.t("fileellipses")
120 yield self.t("fileellipses")
121
121
122 def siblings(self, siblings=[], hiderev=None, **args):
122 def siblings(self, siblings=[], hiderev=None, **args):
123 siblings = [s for s in siblings if s.node() != nullid]
123 siblings = [s for s in siblings if s.node() != nullid]
124 if len(siblings) == 1 and siblings[0].rev() == hiderev:
124 if len(siblings) == 1 and siblings[0].rev() == hiderev:
125 return
125 return
126 for s in siblings:
126 for s in siblings:
127 d = {'node': hex(s.node()), 'rev': s.rev()}
127 d = {'node': hex(s.node()), 'rev': s.rev()}
128 if hasattr(s, 'path'):
128 if hasattr(s, 'path'):
129 d['file'] = s.path()
129 d['file'] = s.path()
130 d.update(args)
130 d.update(args)
131 yield d
131 yield d
132
132
133 def renamelink(self, fl, node):
133 def renamelink(self, fl, node):
134 r = fl.renamed(node)
134 r = fl.renamed(node)
135 if r:
135 if r:
136 return [dict(file=r[0], node=hex(r[1]))]
136 return [dict(file=r[0], node=hex(r[1]))]
137 return []
137 return []
138
138
139 def nodetagsdict(self, node):
139 def nodetagsdict(self, node):
140 return [{"name": i} for i in self.repo.nodetags(node)]
140 return [{"name": i} for i in self.repo.nodetags(node)]
141
141
142 def nodebranchdict(self, ctx):
142 def nodebranchdict(self, ctx):
143 branches = []
143 branches = []
144 branch = ctx.branch()
144 branch = ctx.branch()
145 # If this is an empty repo, ctx.node() == nullid,
145 # If this is an empty repo, ctx.node() == nullid,
146 # ctx.branch() == 'default', but branchtags() is
146 # ctx.branch() == 'default', but branchtags() is
147 # an empty dict. Using dict.get avoids a traceback.
147 # an empty dict. Using dict.get avoids a traceback.
148 if self.repo.branchtags().get(branch) == ctx.node():
148 if self.repo.branchtags().get(branch) == ctx.node():
149 branches.append({"name": branch})
149 branches.append({"name": branch})
150 return branches
150 return branches
151
151
152 def showtag(self, t1, node=nullid, **args):
152 def showtag(self, t1, node=nullid, **args):
153 for t in self.repo.nodetags(node):
153 for t in self.repo.nodetags(node):
154 yield self.t(t1, tag=t, **args)
154 yield self.t(t1, tag=t, **args)
155
155
156 def diff(self, node1, node2, files):
156 def diff(self, node1, node2, files):
157 def filterfiles(filters, files):
157 def filterfiles(filters, files):
158 l = [x for x in files if x in filters]
158 l = [x for x in files if x in filters]
159
159
160 for t in filters:
160 for t in filters:
161 if t and t[-1] != os.sep:
161 if t and t[-1] != os.sep:
162 t += os.sep
162 t += os.sep
163 l += [x for x in files if x.startswith(t)]
163 l += [x for x in files if x.startswith(t)]
164 return l
164 return l
165
165
166 parity = paritygen(self.stripecount)
166 parity = paritygen(self.stripecount)
167 def diffblock(diff, f, fn):
167 def diffblock(diff, f, fn):
168 yield self.t("diffblock",
168 yield self.t("diffblock",
169 lines=prettyprintlines(diff),
169 lines=prettyprintlines(diff),
170 parity=parity.next(),
170 parity=parity.next(),
171 file=f,
171 file=f,
172 filenode=hex(fn or nullid))
172 filenode=hex(fn or nullid))
173
173
174 def prettyprintlines(diff):
174 def prettyprintlines(diff):
175 for l in diff.splitlines(1):
175 for l in diff.splitlines(1):
176 if l.startswith('+'):
176 if l.startswith('+'):
177 yield self.t("difflineplus", line=l)
177 yield self.t("difflineplus", line=l)
178 elif l.startswith('-'):
178 elif l.startswith('-'):
179 yield self.t("difflineminus", line=l)
179 yield self.t("difflineminus", line=l)
180 elif l.startswith('@'):
180 elif l.startswith('@'):
181 yield self.t("difflineat", line=l)
181 yield self.t("difflineat", line=l)
182 else:
182 else:
183 yield self.t("diffline", line=l)
183 yield self.t("diffline", line=l)
184
184
185 r = self.repo
185 r = self.repo
186 c1 = r.changectx(node1)
186 c1 = r.changectx(node1)
187 c2 = r.changectx(node2)
187 c2 = r.changectx(node2)
188 date1 = util.datestr(c1.date())
188 date1 = util.datestr(c1.date())
189 date2 = util.datestr(c2.date())
189 date2 = util.datestr(c2.date())
190
190
191 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
191 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
192 if files:
192 if files:
193 modified, added, removed = map(lambda x: filterfiles(files, x),
193 modified, added, removed = map(lambda x: filterfiles(files, x),
194 (modified, added, removed))
194 (modified, added, removed))
195
195
196 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
196 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
197 for f in modified:
197 for f in modified:
198 to = c1.filectx(f).data()
198 to = c1.filectx(f).data()
199 tn = c2.filectx(f).data()
199 tn = c2.filectx(f).data()
200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
200 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
201 opts=diffopts), f, tn)
201 opts=diffopts), f, tn)
202 for f in added:
202 for f in added:
203 to = None
203 to = None
204 tn = c2.filectx(f).data()
204 tn = c2.filectx(f).data()
205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
205 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
206 opts=diffopts), f, tn)
206 opts=diffopts), f, tn)
207 for f in removed:
207 for f in removed:
208 to = c1.filectx(f).data()
208 to = c1.filectx(f).data()
209 tn = None
209 tn = None
210 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
210 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
211 opts=diffopts), f, tn)
211 opts=diffopts), f, tn)
212
212
213 def changelog(self, ctx, shortlog=False):
213 def changelog(self, ctx, shortlog=False):
214 def changelist(limit=0,**map):
214 def changelist(limit=0,**map):
215 cl = self.repo.changelog
215 cl = self.repo.changelog
216 l = [] # build a list in forward order for efficiency
216 l = [] # build a list in forward order for efficiency
217 for i in xrange(start, end):
217 for i in xrange(start, end):
218 ctx = self.repo.changectx(i)
218 ctx = self.repo.changectx(i)
219 n = ctx.node()
219 n = ctx.node()
220
220
221 l.insert(0, {"parity": parity.next(),
221 l.insert(0, {"parity": parity.next(),
222 "author": ctx.user(),
222 "author": ctx.user(),
223 "parent": self.siblings(ctx.parents(), i - 1),
223 "parent": self.siblings(ctx.parents(), i - 1),
224 "child": self.siblings(ctx.children(), i + 1),
224 "child": self.siblings(ctx.children(), i + 1),
225 "changelogtag": self.showtag("changelogtag",n),
225 "changelogtag": self.showtag("changelogtag",n),
226 "desc": ctx.description(),
226 "desc": ctx.description(),
227 "date": ctx.date(),
227 "date": ctx.date(),
228 "files": self.listfilediffs(ctx.files(), n),
228 "files": self.listfilediffs(ctx.files(), n),
229 "rev": i,
229 "rev": i,
230 "node": hex(n),
230 "node": hex(n),
231 "tags": self.nodetagsdict(n),
231 "tags": self.nodetagsdict(n),
232 "branches": self.nodebranchdict(ctx)})
232 "branches": self.nodebranchdict(ctx)})
233
233
234 if limit > 0:
234 if limit > 0:
235 l = l[:limit]
235 l = l[:limit]
236
236
237 for e in l:
237 for e in l:
238 yield e
238 yield e
239
239
240 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
240 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
241 cl = self.repo.changelog
241 cl = self.repo.changelog
242 count = cl.count()
242 count = cl.count()
243 pos = ctx.rev()
243 pos = ctx.rev()
244 start = max(0, pos - maxchanges + 1)
244 start = max(0, pos - maxchanges + 1)
245 end = min(count, start + maxchanges)
245 end = min(count, start + maxchanges)
246 pos = end - 1
246 pos = end - 1
247 parity = paritygen(self.stripecount, offset=start-end)
247 parity = paritygen(self.stripecount, offset=start-end)
248
248
249 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
249 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
250
250
251 yield self.t(shortlog and 'shortlog' or 'changelog',
251 yield self.t(shortlog and 'shortlog' or 'changelog',
252 changenav=changenav,
252 changenav=changenav,
253 node=hex(cl.tip()),
253 node=hex(cl.tip()),
254 rev=pos, changesets=count,
254 rev=pos, changesets=count,
255 entries=lambda **x: changelist(limit=0,**x),
255 entries=lambda **x: changelist(limit=0,**x),
256 latestentry=lambda **x: changelist(limit=1,**x),
256 latestentry=lambda **x: changelist(limit=1,**x),
257 archives=self.archivelist("tip"))
257 archives=self.archivelist("tip"))
258
258
259 def search(self, query):
259 def search(self, query):
260
260
261 def changelist(**map):
261 def changelist(**map):
262 cl = self.repo.changelog
262 cl = self.repo.changelog
263 count = 0
263 count = 0
264 qw = query.lower().split()
264 qw = query.lower().split()
265
265
266 def revgen():
266 def revgen():
267 for i in xrange(cl.count() - 1, 0, -100):
267 for i in xrange(cl.count() - 1, 0, -100):
268 l = []
268 l = []
269 for j in xrange(max(0, i - 100), i):
269 for j in xrange(max(0, i - 100), i):
270 ctx = self.repo.changectx(j)
270 ctx = self.repo.changectx(j)
271 l.append(ctx)
271 l.append(ctx)
272 l.reverse()
272 l.reverse()
273 for e in l:
273 for e in l:
274 yield e
274 yield e
275
275
276 for ctx in revgen():
276 for ctx in revgen():
277 miss = 0
277 miss = 0
278 for q in qw:
278 for q in qw:
279 if not (q in ctx.user().lower() or
279 if not (q in ctx.user().lower() or
280 q in ctx.description().lower() or
280 q in ctx.description().lower() or
281 q in " ".join(ctx.files()).lower()):
281 q in " ".join(ctx.files()).lower()):
282 miss = 1
282 miss = 1
283 break
283 break
284 if miss:
284 if miss:
285 continue
285 continue
286
286
287 count += 1
287 count += 1
288 n = ctx.node()
288 n = ctx.node()
289
289
290 yield self.t('searchentry',
290 yield self.t('searchentry',
291 parity=parity.next(),
291 parity=parity.next(),
292 author=ctx.user(),
292 author=ctx.user(),
293 parent=self.siblings(ctx.parents()),
293 parent=self.siblings(ctx.parents()),
294 child=self.siblings(ctx.children()),
294 child=self.siblings(ctx.children()),
295 changelogtag=self.showtag("changelogtag",n),
295 changelogtag=self.showtag("changelogtag",n),
296 desc=ctx.description(),
296 desc=ctx.description(),
297 date=ctx.date(),
297 date=ctx.date(),
298 files=self.listfilediffs(ctx.files(), n),
298 files=self.listfilediffs(ctx.files(), n),
299 rev=ctx.rev(),
299 rev=ctx.rev(),
300 node=hex(n),
300 node=hex(n),
301 tags=self.nodetagsdict(n),
301 tags=self.nodetagsdict(n),
302 branches=self.nodebranchdict(ctx))
302 branches=self.nodebranchdict(ctx))
303
303
304 if count >= self.maxchanges:
304 if count >= self.maxchanges:
305 break
305 break
306
306
307 cl = self.repo.changelog
307 cl = self.repo.changelog
308 parity = paritygen(self.stripecount)
308 parity = paritygen(self.stripecount)
309
309
310 yield self.t('search',
310 yield self.t('search',
311 query=query,
311 query=query,
312 node=hex(cl.tip()),
312 node=hex(cl.tip()),
313 entries=changelist,
313 entries=changelist,
314 archives=self.archivelist("tip"))
314 archives=self.archivelist("tip"))
315
315
316 def changeset(self, ctx):
316 def changeset(self, ctx):
317 n = ctx.node()
317 n = ctx.node()
318 parents = ctx.parents()
318 parents = ctx.parents()
319 p1 = parents[0].node()
319 p1 = parents[0].node()
320
320
321 files = []
321 files = []
322 parity = paritygen(self.stripecount)
322 parity = paritygen(self.stripecount)
323 for f in ctx.files():
323 for f in ctx.files():
324 files.append(self.t("filenodelink",
324 files.append(self.t("filenodelink",
325 node=hex(n), file=f,
325 node=hex(n), file=f,
326 parity=parity.next()))
326 parity=parity.next()))
327
327
328 def diff(**map):
328 def diff(**map):
329 yield self.diff(p1, n, None)
329 yield self.diff(p1, n, None)
330
330
331 yield self.t('changeset',
331 yield self.t('changeset',
332 diff=diff,
332 diff=diff,
333 rev=ctx.rev(),
333 rev=ctx.rev(),
334 node=hex(n),
334 node=hex(n),
335 parent=self.siblings(parents),
335 parent=self.siblings(parents),
336 child=self.siblings(ctx.children()),
336 child=self.siblings(ctx.children()),
337 changesettag=self.showtag("changesettag",n),
337 changesettag=self.showtag("changesettag",n),
338 author=ctx.user(),
338 author=ctx.user(),
339 desc=ctx.description(),
339 desc=ctx.description(),
340 date=ctx.date(),
340 date=ctx.date(),
341 files=files,
341 files=files,
342 archives=self.archivelist(hex(n)),
342 archives=self.archivelist(hex(n)),
343 tags=self.nodetagsdict(n),
343 tags=self.nodetagsdict(n),
344 branches=self.nodebranchdict(ctx))
344 branches=self.nodebranchdict(ctx))
345
345
346 def filelog(self, fctx):
346 def filelog(self, fctx):
347 f = fctx.path()
347 f = fctx.path()
348 fl = fctx.filelog()
348 fl = fctx.filelog()
349 count = fl.count()
349 count = fl.count()
350 pagelen = self.maxshortchanges
350 pagelen = self.maxshortchanges
351 pos = fctx.filerev()
351 pos = fctx.filerev()
352 start = max(0, pos - pagelen + 1)
352 start = max(0, pos - pagelen + 1)
353 end = min(count, start + pagelen)
353 end = min(count, start + pagelen)
354 pos = end - 1
354 pos = end - 1
355 parity = paritygen(self.stripecount, offset=start-end)
355 parity = paritygen(self.stripecount, offset=start-end)
356
356
357 def entries(limit=0, **map):
357 def entries(limit=0, **map):
358 l = []
358 l = []
359
359
360 for i in xrange(start, end):
360 for i in xrange(start, end):
361 ctx = fctx.filectx(i)
361 ctx = fctx.filectx(i)
362 n = fl.node(i)
362 n = fl.node(i)
363
363
364 l.insert(0, {"parity": parity.next(),
364 l.insert(0, {"parity": parity.next(),
365 "filerev": i,
365 "filerev": i,
366 "file": f,
366 "file": f,
367 "node": hex(ctx.node()),
367 "node": hex(ctx.node()),
368 "author": ctx.user(),
368 "author": ctx.user(),
369 "date": ctx.date(),
369 "date": ctx.date(),
370 "rename": self.renamelink(fl, n),
370 "rename": self.renamelink(fl, n),
371 "parent": self.siblings(fctx.parents()),
371 "parent": self.siblings(fctx.parents()),
372 "child": self.siblings(fctx.children()),
372 "child": self.siblings(fctx.children()),
373 "desc": ctx.description()})
373 "desc": ctx.description()})
374
374
375 if limit > 0:
375 if limit > 0:
376 l = l[:limit]
376 l = l[:limit]
377
377
378 for e in l:
378 for e in l:
379 yield e
379 yield e
380
380
381 nodefunc = lambda x: fctx.filectx(fileid=x)
381 nodefunc = lambda x: fctx.filectx(fileid=x)
382 nav = revnavgen(pos, pagelen, count, nodefunc)
382 nav = revnavgen(pos, pagelen, count, nodefunc)
383 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
383 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
384 entries=lambda **x: entries(limit=0, **x),
384 entries=lambda **x: entries(limit=0, **x),
385 latestentry=lambda **x: entries(limit=1, **x))
385 latestentry=lambda **x: entries(limit=1, **x))
386
386
387 def filerevision(self, fctx):
387 def filerevision(self, fctx):
388 f = fctx.path()
388 f = fctx.path()
389 text = fctx.data()
389 text = fctx.data()
390 fl = fctx.filelog()
390 fl = fctx.filelog()
391 n = fctx.filenode()
391 n = fctx.filenode()
392 parity = paritygen(self.stripecount)
392 parity = paritygen(self.stripecount)
393
393
394 mt = mimetypes.guess_type(f)[0]
394 mt = mimetypes.guess_type(f)[0]
395 rawtext = text
395 rawtext = text
396 if util.binary(text):
396 if util.binary(text):
397 mt = mt or 'application/octet-stream'
397 mt = mt or 'application/octet-stream'
398 text = "(binary:%s)" % mt
398 text = "(binary:%s)" % mt
399 mt = mt or 'text/plain'
399 mt = mt or 'text/plain'
400
400
401 def lines():
401 def lines():
402 for l, t in enumerate(text.splitlines(1)):
402 for l, t in enumerate(text.splitlines(1)):
403 yield {"line": t,
403 yield {"line": t,
404 "linenumber": "% 6d" % (l + 1),
404 "linenumber": "% 6d" % (l + 1),
405 "parity": parity.next()}
405 "parity": parity.next()}
406
406
407 yield self.t("filerevision",
407 yield self.t("filerevision",
408 file=f,
408 file=f,
409 path=_up(f),
409 path=_up(f),
410 text=lines(),
410 text=lines(),
411 raw=rawtext,
411 raw=rawtext,
412 mimetype=mt,
412 mimetype=mt,
413 rev=fctx.rev(),
413 rev=fctx.rev(),
414 node=hex(fctx.node()),
414 node=hex(fctx.node()),
415 author=fctx.user(),
415 author=fctx.user(),
416 date=fctx.date(),
416 date=fctx.date(),
417 desc=fctx.description(),
417 desc=fctx.description(),
418 parent=self.siblings(fctx.parents()),
418 parent=self.siblings(fctx.parents()),
419 child=self.siblings(fctx.children()),
419 child=self.siblings(fctx.children()),
420 rename=self.renamelink(fl, n),
420 rename=self.renamelink(fl, n),
421 permissions=fctx.manifest().flags(f))
421 permissions=fctx.manifest().flags(f))
422
422
423 def fileannotate(self, fctx):
423 def fileannotate(self, fctx):
424 f = fctx.path()
424 f = fctx.path()
425 n = fctx.filenode()
425 n = fctx.filenode()
426 fl = fctx.filelog()
426 fl = fctx.filelog()
427 parity = paritygen(self.stripecount)
427 parity = paritygen(self.stripecount)
428
428
429 def annotate(**map):
429 def annotate(**map):
430 last = None
430 last = None
431 for f, l in fctx.annotate(follow=True):
431 for f, l in fctx.annotate(follow=True):
432 fnode = f.filenode()
432 fnode = f.filenode()
433 name = self.repo.ui.shortuser(f.user())
433 name = self.repo.ui.shortuser(f.user())
434
434
435 if last != fnode:
435 if last != fnode:
436 last = fnode
436 last = fnode
437
437
438 yield {"parity": parity.next(),
438 yield {"parity": parity.next(),
439 "node": hex(f.node()),
439 "node": hex(f.node()),
440 "rev": f.rev(),
440 "rev": f.rev(),
441 "author": name,
441 "author": name,
442 "file": f.path(),
442 "file": f.path(),
443 "line": l}
443 "line": l}
444
444
445 yield self.t("fileannotate",
445 yield self.t("fileannotate",
446 file=f,
446 file=f,
447 annotate=annotate,
447 annotate=annotate,
448 path=_up(f),
448 path=_up(f),
449 rev=fctx.rev(),
449 rev=fctx.rev(),
450 node=hex(fctx.node()),
450 node=hex(fctx.node()),
451 author=fctx.user(),
451 author=fctx.user(),
452 date=fctx.date(),
452 date=fctx.date(),
453 desc=fctx.description(),
453 desc=fctx.description(),
454 rename=self.renamelink(fl, n),
454 rename=self.renamelink(fl, n),
455 parent=self.siblings(fctx.parents()),
455 parent=self.siblings(fctx.parents()),
456 child=self.siblings(fctx.children()),
456 child=self.siblings(fctx.children()),
457 permissions=fctx.manifest().flags(f))
457 permissions=fctx.manifest().flags(f))
458
458
459 def manifest(self, ctx, path):
459 def manifest(self, ctx, path):
460 mf = ctx.manifest()
460 mf = ctx.manifest()
461 node = ctx.node()
461 node = ctx.node()
462
462
463 files = {}
463 files = {}
464 parity = paritygen(self.stripecount)
464 parity = paritygen(self.stripecount)
465
465
466 if path and path[-1] != "/":
466 if path and path[-1] != "/":
467 path += "/"
467 path += "/"
468 l = len(path)
468 l = len(path)
469 abspath = "/" + path
469 abspath = "/" + path
470
470
471 for f, n in mf.items():
471 for f, n in mf.items():
472 if f[:l] != path:
472 if f[:l] != path:
473 continue
473 continue
474 remain = f[l:]
474 remain = f[l:]
475 if "/" in remain:
475 if "/" in remain:
476 short = remain[:remain.index("/") + 1] # bleah
476 short = remain[:remain.index("/") + 1] # bleah
477 files[short] = (f, None)
477 files[short] = (f, None)
478 else:
478 else:
479 short = os.path.basename(remain)
479 short = os.path.basename(remain)
480 files[short] = (f, n)
480 files[short] = (f, n)
481
481
482 if not files:
482 if not files:
483 raise ErrorResponse(404, 'Path not found: ' + path)
483 raise ErrorResponse(404, 'Path not found: ' + path)
484
484
485 def filelist(**map):
485 def filelist(**map):
486 fl = files.keys()
486 fl = files.keys()
487 fl.sort()
487 fl.sort()
488 for f in fl:
488 for f in fl:
489 full, fnode = files[f]
489 full, fnode = files[f]
490 if not fnode:
490 if not fnode:
491 continue
491 continue
492
492
493 fctx = ctx.filectx(full)
493 fctx = ctx.filectx(full)
494 yield {"file": full,
494 yield {"file": full,
495 "parity": parity.next(),
495 "parity": parity.next(),
496 "basename": f,
496 "basename": f,
497 "date": fctx.changectx().date(),
497 "date": fctx.changectx().date(),
498 "size": fctx.size(),
498 "size": fctx.size(),
499 "permissions": mf.flags(full)}
499 "permissions": mf.flags(full)}
500
500
501 def dirlist(**map):
501 def dirlist(**map):
502 fl = files.keys()
502 fl = files.keys()
503 fl.sort()
503 fl.sort()
504 for f in fl:
504 for f in fl:
505 full, fnode = files[f]
505 full, fnode = files[f]
506 if fnode:
506 if fnode:
507 continue
507 continue
508
508
509 yield {"parity": parity.next(),
509 yield {"parity": parity.next(),
510 "path": "%s%s" % (abspath, f),
510 "path": "%s%s" % (abspath, f),
511 "basename": f[:-1]}
511 "basename": f[:-1]}
512
512
513 yield self.t("manifest",
513 yield self.t("manifest",
514 rev=ctx.rev(),
514 rev=ctx.rev(),
515 node=hex(node),
515 node=hex(node),
516 path=abspath,
516 path=abspath,
517 up=_up(abspath),
517 up=_up(abspath),
518 upparity=parity.next(),
518 upparity=parity.next(),
519 fentries=filelist,
519 fentries=filelist,
520 dentries=dirlist,
520 dentries=dirlist,
521 archives=self.archivelist(hex(node)),
521 archives=self.archivelist(hex(node)),
522 tags=self.nodetagsdict(node),
522 tags=self.nodetagsdict(node),
523 branches=self.nodebranchdict(ctx))
523 branches=self.nodebranchdict(ctx))
524
524
525 def tags(self):
525 def tags(self):
526 i = self.repo.tagslist()
526 i = self.repo.tagslist()
527 i.reverse()
527 i.reverse()
528 parity = paritygen(self.stripecount)
528 parity = paritygen(self.stripecount)
529
529
530 def entries(notip=False,limit=0, **map):
530 def entries(notip=False,limit=0, **map):
531 count = 0
531 count = 0
532 for k, n in i:
532 for k, n in i:
533 if notip and k == "tip":
533 if notip and k == "tip":
534 continue
534 continue
535 if limit > 0 and count >= limit:
535 if limit > 0 and count >= limit:
536 continue
536 continue
537 count = count + 1
537 count = count + 1
538 yield {"parity": parity.next(),
538 yield {"parity": parity.next(),
539 "tag": k,
539 "tag": k,
540 "date": self.repo.changectx(n).date(),
540 "date": self.repo.changectx(n).date(),
541 "node": hex(n)}
541 "node": hex(n)}
542
542
543 yield self.t("tags",
543 yield self.t("tags",
544 node=hex(self.repo.changelog.tip()),
544 node=hex(self.repo.changelog.tip()),
545 entries=lambda **x: entries(False,0, **x),
545 entries=lambda **x: entries(False,0, **x),
546 entriesnotip=lambda **x: entries(True,0, **x),
546 entriesnotip=lambda **x: entries(True,0, **x),
547 latestentry=lambda **x: entries(True,1, **x))
547 latestentry=lambda **x: entries(True,1, **x))
548
548
549 def summary(self):
549 def summary(self):
550 i = self.repo.tagslist()
550 i = self.repo.tagslist()
551 i.reverse()
551 i.reverse()
552
552
553 def tagentries(**map):
553 def tagentries(**map):
554 parity = paritygen(self.stripecount)
554 parity = paritygen(self.stripecount)
555 count = 0
555 count = 0
556 for k, n in i:
556 for k, n in i:
557 if k == "tip": # skip tip
557 if k == "tip": # skip tip
558 continue;
558 continue;
559
559
560 count += 1
560 count += 1
561 if count > 10: # limit to 10 tags
561 if count > 10: # limit to 10 tags
562 break;
562 break;
563
563
564 yield self.t("tagentry",
564 yield self.t("tagentry",
565 parity=parity.next(),
565 parity=parity.next(),
566 tag=k,
566 tag=k,
567 node=hex(n),
567 node=hex(n),
568 date=self.repo.changectx(n).date())
568 date=self.repo.changectx(n).date())
569
569
570
570
571 def branches(**map):
571 def branches(**map):
572 parity = paritygen(self.stripecount)
572 parity = paritygen(self.stripecount)
573
573
574 b = self.repo.branchtags()
574 b = self.repo.branchtags()
575 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
575 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
576 l.sort()
576 l.sort()
577
577
578 for r,n,t in l:
578 for r,n,t in l:
579 ctx = self.repo.changectx(n)
579 ctx = self.repo.changectx(n)
580
580
581 yield {'parity': parity.next(),
581 yield {'parity': parity.next(),
582 'branch': t,
582 'branch': t,
583 'node': hex(n),
583 'node': hex(n),
584 'date': ctx.date()}
584 'date': ctx.date()}
585
585
586 def changelist(**map):
586 def changelist(**map):
587 parity = paritygen(self.stripecount, offset=start-end)
587 parity = paritygen(self.stripecount, offset=start-end)
588 l = [] # build a list in forward order for efficiency
588 l = [] # build a list in forward order for efficiency
589 for i in xrange(start, end):
589 for i in xrange(start, end):
590 ctx = self.repo.changectx(i)
590 ctx = self.repo.changectx(i)
591 n = ctx.node()
591 n = ctx.node()
592 hn = hex(n)
592 hn = hex(n)
593
593
594 l.insert(0, self.t(
594 l.insert(0, self.t(
595 'shortlogentry',
595 'shortlogentry',
596 parity=parity.next(),
596 parity=parity.next(),
597 author=ctx.user(),
597 author=ctx.user(),
598 desc=ctx.description(),
598 desc=ctx.description(),
599 date=ctx.date(),
599 date=ctx.date(),
600 rev=i,
600 rev=i,
601 node=hn,
601 node=hn,
602 tags=self.nodetagsdict(n),
602 tags=self.nodetagsdict(n),
603 branches=self.nodebranchdict(ctx)))
603 branches=self.nodebranchdict(ctx)))
604
604
605 yield l
605 yield l
606
606
607 cl = self.repo.changelog
607 cl = self.repo.changelog
608 count = cl.count()
608 count = cl.count()
609 start = max(0, count - self.maxchanges)
609 start = max(0, count - self.maxchanges)
610 end = min(count, start + self.maxchanges)
610 end = min(count, start + self.maxchanges)
611
611
612 yield self.t("summary",
612 yield self.t("summary",
613 desc=self.config("web", "description", "unknown"),
613 desc=self.config("web", "description", "unknown"),
614 owner=(self.config("ui", "username") or # preferred
614 owner=(self.config("ui", "username") or # preferred
615 self.config("web", "contact") or # deprecated
615 self.config("web", "contact") or # deprecated
616 self.config("web", "author", "unknown")), # also
616 self.config("web", "author", "unknown")), # also
617 lastchange=cl.read(cl.tip())[2],
617 lastchange=cl.read(cl.tip())[2],
618 tags=tagentries,
618 tags=tagentries,
619 branches=branches,
619 branches=branches,
620 shortlog=changelist,
620 shortlog=changelist,
621 node=hex(cl.tip()),
621 node=hex(cl.tip()),
622 archives=self.archivelist("tip"))
622 archives=self.archivelist("tip"))
623
623
624 def filediff(self, fctx):
624 def filediff(self, fctx):
625 n = fctx.node()
625 n = fctx.node()
626 path = fctx.path()
626 path = fctx.path()
627 parents = fctx.parents()
627 parents = fctx.parents()
628 p1 = parents and parents[0].node() or nullid
628 p1 = parents and parents[0].node() or nullid
629
629
630 def diff(**map):
630 def diff(**map):
631 yield self.diff(p1, n, [path])
631 yield self.diff(p1, n, [path])
632
632
633 yield self.t("filediff",
633 yield self.t("filediff",
634 file=path,
634 file=path,
635 node=hex(n),
635 node=hex(n),
636 rev=fctx.rev(),
636 rev=fctx.rev(),
637 parent=self.siblings(parents),
637 parent=self.siblings(parents),
638 child=self.siblings(fctx.children()),
638 child=self.siblings(fctx.children()),
639 diff=diff)
639 diff=diff)
640
640
641 archive_specs = {
641 archive_specs = {
642 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
642 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
643 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
643 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
644 'zip': ('application/zip', 'zip', '.zip', None),
644 'zip': ('application/zip', 'zip', '.zip', None),
645 }
645 }
646
646
647 def archive(self, req, key, type_):
647 def archive(self, req, key, type_):
648 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
648 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
649 cnode = self.repo.lookup(key)
649 cnode = self.repo.lookup(key)
650 arch_version = key
650 arch_version = key
651 if cnode == key or key == 'tip':
651 if cnode == key or key == 'tip':
652 arch_version = short(cnode)
652 arch_version = short(cnode)
653 name = "%s-%s" % (reponame, arch_version)
653 name = "%s-%s" % (reponame, arch_version)
654 mimetype, artype, extension, encoding = self.archive_specs[type_]
654 mimetype, artype, extension, encoding = self.archive_specs[type_]
655 headers = [('Content-type', mimetype),
655 headers = [('Content-type', mimetype),
656 ('Content-disposition', 'attachment; filename=%s%s' %
656 ('Content-disposition', 'attachment; filename=%s%s' %
657 (name, extension))]
657 (name, extension))]
658 if encoding:
658 if encoding:
659 headers.append(('Content-encoding', encoding))
659 headers.append(('Content-encoding', encoding))
660 req.header(headers)
660 req.header(headers)
661 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
661 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
662
662
663 # add tags to things
663 # add tags to things
664 # tags -> list of changesets corresponding to tags
664 # tags -> list of changesets corresponding to tags
665 # find tag, changeset, file
665 # find tag, changeset, file
666
666
667 def cleanpath(self, path):
667 def cleanpath(self, path):
668 path = path.lstrip('/')
668 path = path.lstrip('/')
669 return util.canonpath(self.repo.root, '', path)
669 return util.canonpath(self.repo.root, '', path)
670
670
671 def run(self):
671 def run(self):
672 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
672 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
673 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
673 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
674 import mercurial.hgweb.wsgicgi as wsgicgi
674 import mercurial.hgweb.wsgicgi as wsgicgi
675 wsgicgi.launch(self)
675 wsgicgi.launch(self)
676
676
677 def __call__(self, env, respond):
677 def __call__(self, env, respond):
678 req = wsgirequest(env, respond)
678 req = wsgirequest(env, respond)
679 self.run_wsgi(req)
679 self.run_wsgi(req)
680 return req
680 return req
681
681
682 def run_wsgi(self, req):
682 def run_wsgi(self, req):
683 def header(**map):
683 def header(**map):
684 header_file = cStringIO.StringIO(
684 header_file = cStringIO.StringIO(
685 ''.join(self.t("header", encoding=self.encoding, **map)))
685 ''.join(self.t("header", encoding=self.encoding, **map)))
686 msg = mimetools.Message(header_file, 0)
686 msg = mimetools.Message(header_file, 0)
687 req.header(msg.items())
687 req.header(msg.items())
688 yield header_file.read()
688 yield header_file.read()
689
689
690 def rawfileheader(**map):
690 def rawfileheader(**map):
691 req.header([('Content-type', map['mimetype']),
691 req.header([('Content-type', map['mimetype']),
692 ('Content-disposition', 'filename=%s' % map['file']),
692 ('Content-disposition', 'filename=%s' % map['file']),
693 ('Content-length', str(len(map['raw'])))])
693 ('Content-length', str(len(map['raw'])))])
694 yield ''
694 yield ''
695
695
696 def footer(**map):
696 def footer(**map):
697 yield self.t("footer", **map)
697 yield self.t("footer", **map)
698
698
699 def motd(**map):
699 def motd(**map):
700 yield self.config("web", "motd", "")
700 yield self.config("web", "motd", "")
701
701
702 def expand_form(form):
702 def expand_form(form):
703 shortcuts = {
703 shortcuts = {
704 'cl': [('cmd', ['changelog']), ('rev', None)],
704 'cl': [('cmd', ['changelog']), ('rev', None)],
705 'sl': [('cmd', ['shortlog']), ('rev', None)],
705 'sl': [('cmd', ['shortlog']), ('rev', None)],
706 'cs': [('cmd', ['changeset']), ('node', None)],
706 'cs': [('cmd', ['changeset']), ('node', None)],
707 'f': [('cmd', ['file']), ('filenode', None)],
707 'f': [('cmd', ['file']), ('filenode', None)],
708 'fl': [('cmd', ['filelog']), ('filenode', None)],
708 'fl': [('cmd', ['filelog']), ('filenode', None)],
709 'fd': [('cmd', ['filediff']), ('node', None)],
709 'fd': [('cmd', ['filediff']), ('node', None)],
710 'fa': [('cmd', ['annotate']), ('filenode', None)],
710 'fa': [('cmd', ['annotate']), ('filenode', None)],
711 'mf': [('cmd', ['manifest']), ('manifest', None)],
711 'mf': [('cmd', ['manifest']), ('manifest', None)],
712 'ca': [('cmd', ['archive']), ('node', None)],
712 'ca': [('cmd', ['archive']), ('node', None)],
713 'tags': [('cmd', ['tags'])],
713 'tags': [('cmd', ['tags'])],
714 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
714 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
715 'static': [('cmd', ['static']), ('file', None)]
715 'static': [('cmd', ['static']), ('file', None)]
716 }
716 }
717
717
718 for k in shortcuts.iterkeys():
718 for k in shortcuts.iterkeys():
719 if form.has_key(k):
719 if form.has_key(k):
720 for name, value in shortcuts[k]:
720 for name, value in shortcuts[k]:
721 if value is None:
721 if value is None:
722 value = form[k]
722 value = form[k]
723 form[name] = value
723 form[name] = value
724 del form[k]
724 del form[k]
725
725
726 def rewrite_request(req):
726 def rewrite_request(req):
727 '''translate new web interface to traditional format'''
727 '''translate new web interface to traditional format'''
728
728
729 def spliturl(req):
729 req.url = req.env['SCRIPT_NAME']
730 def firstitem(query):
730 if not req.url.endswith('/'):
731 return query.split('&', 1)[0].split(';', 1)[0]
731 req.url += '/'
732
732 if req.env.has_key('REPO_NAME'):
733 def normurl(url):
733 req.url += req.env['REPO_NAME'] + '/'
734 inner = '/'.join([x for x in url.split('/') if x])
734
735 tl = len(url) > 1 and url.endswith('/') and '/' or ''
735 if req.env.get('PATH_INFO'):
736
736 parts = req.env.get('PATH_INFO').strip('/').split('/')
737 return '%s%s%s' % (url.startswith('/') and '/' or '',
737 repo_parts = req.env.get('REPO_NAME', '').split('/')
738 inner, tl)
738 if parts[:len(repo_parts)] == repo_parts:
739
739 parts = parts[len(repo_parts):]
740 root = normurl(urllib.unquote(req.env.get('REQUEST_URI', '').split('?', 1)[0]))
740 query = '/'.join(parts)
741 pi = normurl(req.env.get('PATH_INFO', ''))
741 else:
742 if pi:
742 query = req.env['QUERY_STRING'].split('&', 1)[0]
743 # strip leading /
743 query = query.split(';', 1)[0]
744 pi = pi[1:]
745 if pi:
746 root = root[:root.rfind(pi)]
747 if req.env.has_key('REPO_NAME'):
748 rn = req.env['REPO_NAME'] + '/'
749 root += rn
750 query = pi[len(rn):]
751 else:
752 query = pi
753 else:
754 root += '?'
755 query = firstitem(req.env['QUERY_STRING'])
756
757 return (root, query)
758
759 req.url, query = spliturl(req)
760
744
761 if req.form.has_key('cmd'):
745 if req.form.has_key('cmd'):
762 # old style
746 # old style
763 return
747 return
764
748
765 args = query.split('/', 2)
749 args = query.split('/', 2)
766 if not args or not args[0]:
750 if not args or not args[0]:
767 return
751 return
768
752
769 cmd = args.pop(0)
753 cmd = args.pop(0)
770 style = cmd.rfind('-')
754 style = cmd.rfind('-')
771 if style != -1:
755 if style != -1:
772 req.form['style'] = [cmd[:style]]
756 req.form['style'] = [cmd[:style]]
773 cmd = cmd[style+1:]
757 cmd = cmd[style+1:]
774 # avoid accepting e.g. style parameter as command
758 # avoid accepting e.g. style parameter as command
775 if hasattr(self, 'do_' + cmd):
759 if hasattr(self, 'do_' + cmd):
776 req.form['cmd'] = [cmd]
760 req.form['cmd'] = [cmd]
777
761
778 if args and args[0]:
762 if args and args[0]:
779 node = args.pop(0)
763 node = args.pop(0)
780 req.form['node'] = [node]
764 req.form['node'] = [node]
781 if args:
765 if args:
782 req.form['file'] = args
766 req.form['file'] = args
783
767
784 if cmd == 'static':
768 if cmd == 'static':
785 req.form['file'] = req.form['node']
769 req.form['file'] = req.form['node']
786 elif cmd == 'archive':
770 elif cmd == 'archive':
787 fn = req.form['node'][0]
771 fn = req.form['node'][0]
788 for type_, spec in self.archive_specs.iteritems():
772 for type_, spec in self.archive_specs.iteritems():
789 ext = spec[2]
773 ext = spec[2]
790 if fn.endswith(ext):
774 if fn.endswith(ext):
791 req.form['node'] = [fn[:-len(ext)]]
775 req.form['node'] = [fn[:-len(ext)]]
792 req.form['type'] = [type_]
776 req.form['type'] = [type_]
793
777
794 def sessionvars(**map):
778 def sessionvars(**map):
795 fields = []
779 fields = []
796 if req.form.has_key('style'):
780 if req.form.has_key('style'):
797 style = req.form['style'][0]
781 style = req.form['style'][0]
798 if style != self.config('web', 'style', ''):
782 if style != self.config('web', 'style', ''):
799 fields.append(('style', style))
783 fields.append(('style', style))
800
784
801 separator = req.url[-1] == '?' and ';' or '?'
785 separator = req.url[-1] == '?' and ';' or '?'
802 for name, value in fields:
786 for name, value in fields:
803 yield dict(name=name, value=value, separator=separator)
787 yield dict(name=name, value=value, separator=separator)
804 separator = ';'
788 separator = ';'
805
789
806 self.refresh()
790 self.refresh()
807
791
808 expand_form(req.form)
792 expand_form(req.form)
809 rewrite_request(req)
793 rewrite_request(req)
810
794
811 style = self.config("web", "style", "")
795 style = self.config("web", "style", "")
812 if req.form.has_key('style'):
796 if req.form.has_key('style'):
813 style = req.form['style'][0]
797 style = req.form['style'][0]
814 mapfile = style_map(self.templatepath, style)
798 mapfile = style_map(self.templatepath, style)
815
799
816 proto = req.env.get('wsgi.url_scheme')
800 proto = req.env.get('wsgi.url_scheme')
817 if proto == 'https':
801 if proto == 'https':
818 proto = 'https'
802 proto = 'https'
819 default_port = "443"
803 default_port = "443"
820 else:
804 else:
821 proto = 'http'
805 proto = 'http'
822 default_port = "80"
806 default_port = "80"
823
807
824 port = req.env["SERVER_PORT"]
808 port = req.env["SERVER_PORT"]
825 port = port != default_port and (":" + port) or ""
809 port = port != default_port and (":" + port) or ""
826 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
810 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
827 staticurl = self.config("web", "staticurl") or req.url + 'static/'
811 staticurl = self.config("web", "staticurl") or req.url + 'static/'
828 if not staticurl.endswith('/'):
812 if not staticurl.endswith('/'):
829 staticurl += '/'
813 staticurl += '/'
830
814
831 if not self.reponame:
815 if not self.reponame:
832 self.reponame = (self.config("web", "name")
816 self.reponame = (self.config("web", "name")
833 or req.env.get('REPO_NAME')
817 or req.env.get('REPO_NAME')
834 or req.url.strip('/') or self.repo.root)
818 or req.url.strip('/') or self.repo.root)
835
819
836 self.t = templater.templater(mapfile, templater.common_filters,
820 self.t = templater.templater(mapfile, templater.common_filters,
837 defaults={"url": req.url,
821 defaults={"url": req.url,
838 "staticurl": staticurl,
822 "staticurl": staticurl,
839 "urlbase": urlbase,
823 "urlbase": urlbase,
840 "repo": self.reponame,
824 "repo": self.reponame,
841 "header": header,
825 "header": header,
842 "footer": footer,
826 "footer": footer,
843 "motd": motd,
827 "motd": motd,
844 "rawfileheader": rawfileheader,
828 "rawfileheader": rawfileheader,
845 "sessionvars": sessionvars
829 "sessionvars": sessionvars
846 })
830 })
847
831
848 try:
832 try:
849 if not req.form.has_key('cmd'):
833 if not req.form.has_key('cmd'):
850 req.form['cmd'] = [self.t.cache['default']]
834 req.form['cmd'] = [self.t.cache['default']]
851
835
852 cmd = req.form['cmd'][0]
836 cmd = req.form['cmd'][0]
853
837
854 try:
838 try:
855 method = getattr(self, 'do_' + cmd)
839 method = getattr(self, 'do_' + cmd)
856 method(req)
840 method(req)
857 except revlog.LookupError, err:
841 except revlog.LookupError, err:
858 req.respond(404, self.t(
842 req.respond(404, self.t(
859 'error', error='revision not found: %s' % err.name))
843 'error', error='revision not found: %s' % err.name))
860 except (hg.RepoError, revlog.RevlogError), inst:
844 except (hg.RepoError, revlog.RevlogError), inst:
861 req.respond('500 Internal Server Error',
845 req.respond('500 Internal Server Error',
862 self.t('error', error=str(inst)))
846 self.t('error', error=str(inst)))
863 except ErrorResponse, inst:
847 except ErrorResponse, inst:
864 req.respond(inst.code, self.t('error', error=inst.message))
848 req.respond(inst.code, self.t('error', error=inst.message))
865 except AttributeError:
849 except AttributeError:
866 req.respond(400,
850 req.respond(400,
867 self.t('error', error='No such method: ' + cmd))
851 self.t('error', error='No such method: ' + cmd))
868 finally:
852 finally:
869 self.t = None
853 self.t = None
870
854
871 def changectx(self, req):
855 def changectx(self, req):
872 if req.form.has_key('node'):
856 if req.form.has_key('node'):
873 changeid = req.form['node'][0]
857 changeid = req.form['node'][0]
874 elif req.form.has_key('manifest'):
858 elif req.form.has_key('manifest'):
875 changeid = req.form['manifest'][0]
859 changeid = req.form['manifest'][0]
876 else:
860 else:
877 changeid = self.repo.changelog.count() - 1
861 changeid = self.repo.changelog.count() - 1
878
862
879 try:
863 try:
880 ctx = self.repo.changectx(changeid)
864 ctx = self.repo.changectx(changeid)
881 except hg.RepoError:
865 except hg.RepoError:
882 man = self.repo.manifest
866 man = self.repo.manifest
883 mn = man.lookup(changeid)
867 mn = man.lookup(changeid)
884 ctx = self.repo.changectx(man.linkrev(mn))
868 ctx = self.repo.changectx(man.linkrev(mn))
885
869
886 return ctx
870 return ctx
887
871
888 def filectx(self, req):
872 def filectx(self, req):
889 path = self.cleanpath(req.form['file'][0])
873 path = self.cleanpath(req.form['file'][0])
890 if req.form.has_key('node'):
874 if req.form.has_key('node'):
891 changeid = req.form['node'][0]
875 changeid = req.form['node'][0]
892 else:
876 else:
893 changeid = req.form['filenode'][0]
877 changeid = req.form['filenode'][0]
894 try:
878 try:
895 ctx = self.repo.changectx(changeid)
879 ctx = self.repo.changectx(changeid)
896 fctx = ctx.filectx(path)
880 fctx = ctx.filectx(path)
897 except hg.RepoError:
881 except hg.RepoError:
898 fctx = self.repo.filectx(path, fileid=changeid)
882 fctx = self.repo.filectx(path, fileid=changeid)
899
883
900 return fctx
884 return fctx
901
885
902 def do_log(self, req):
886 def do_log(self, req):
903 if req.form.has_key('file') and req.form['file'][0]:
887 if req.form.has_key('file') and req.form['file'][0]:
904 self.do_filelog(req)
888 self.do_filelog(req)
905 else:
889 else:
906 self.do_changelog(req)
890 self.do_changelog(req)
907
891
908 def do_rev(self, req):
892 def do_rev(self, req):
909 self.do_changeset(req)
893 self.do_changeset(req)
910
894
911 def do_file(self, req):
895 def do_file(self, req):
912 path = self.cleanpath(req.form.get('file', [''])[0])
896 path = self.cleanpath(req.form.get('file', [''])[0])
913 if path:
897 if path:
914 try:
898 try:
915 req.write(self.filerevision(self.filectx(req)))
899 req.write(self.filerevision(self.filectx(req)))
916 return
900 return
917 except revlog.LookupError:
901 except revlog.LookupError:
918 pass
902 pass
919
903
920 req.write(self.manifest(self.changectx(req), path))
904 req.write(self.manifest(self.changectx(req), path))
921
905
922 def do_diff(self, req):
906 def do_diff(self, req):
923 self.do_filediff(req)
907 self.do_filediff(req)
924
908
925 def do_changelog(self, req, shortlog = False):
909 def do_changelog(self, req, shortlog = False):
926 if req.form.has_key('node'):
910 if req.form.has_key('node'):
927 ctx = self.changectx(req)
911 ctx = self.changectx(req)
928 else:
912 else:
929 if req.form.has_key('rev'):
913 if req.form.has_key('rev'):
930 hi = req.form['rev'][0]
914 hi = req.form['rev'][0]
931 else:
915 else:
932 hi = self.repo.changelog.count() - 1
916 hi = self.repo.changelog.count() - 1
933 try:
917 try:
934 ctx = self.repo.changectx(hi)
918 ctx = self.repo.changectx(hi)
935 except hg.RepoError:
919 except hg.RepoError:
936 req.write(self.search(hi)) # XXX redirect to 404 page?
920 req.write(self.search(hi)) # XXX redirect to 404 page?
937 return
921 return
938
922
939 req.write(self.changelog(ctx, shortlog = shortlog))
923 req.write(self.changelog(ctx, shortlog = shortlog))
940
924
941 def do_shortlog(self, req):
925 def do_shortlog(self, req):
942 self.do_changelog(req, shortlog = True)
926 self.do_changelog(req, shortlog = True)
943
927
944 def do_changeset(self, req):
928 def do_changeset(self, req):
945 req.write(self.changeset(self.changectx(req)))
929 req.write(self.changeset(self.changectx(req)))
946
930
947 def do_manifest(self, req):
931 def do_manifest(self, req):
948 req.write(self.manifest(self.changectx(req),
932 req.write(self.manifest(self.changectx(req),
949 self.cleanpath(req.form['path'][0])))
933 self.cleanpath(req.form['path'][0])))
950
934
951 def do_tags(self, req):
935 def do_tags(self, req):
952 req.write(self.tags())
936 req.write(self.tags())
953
937
954 def do_summary(self, req):
938 def do_summary(self, req):
955 req.write(self.summary())
939 req.write(self.summary())
956
940
957 def do_filediff(self, req):
941 def do_filediff(self, req):
958 req.write(self.filediff(self.filectx(req)))
942 req.write(self.filediff(self.filectx(req)))
959
943
960 def do_annotate(self, req):
944 def do_annotate(self, req):
961 req.write(self.fileannotate(self.filectx(req)))
945 req.write(self.fileannotate(self.filectx(req)))
962
946
963 def do_filelog(self, req):
947 def do_filelog(self, req):
964 req.write(self.filelog(self.filectx(req)))
948 req.write(self.filelog(self.filectx(req)))
965
949
966 def do_lookup(self, req):
950 def do_lookup(self, req):
967 try:
951 try:
968 r = hex(self.repo.lookup(req.form['key'][0]))
952 r = hex(self.repo.lookup(req.form['key'][0]))
969 success = 1
953 success = 1
970 except Exception,inst:
954 except Exception,inst:
971 r = str(inst)
955 r = str(inst)
972 success = 0
956 success = 0
973 resp = "%s %s\n" % (success, r)
957 resp = "%s %s\n" % (success, r)
974 req.httphdr("application/mercurial-0.1", length=len(resp))
958 req.httphdr("application/mercurial-0.1", length=len(resp))
975 req.write(resp)
959 req.write(resp)
976
960
977 def do_heads(self, req):
961 def do_heads(self, req):
978 resp = " ".join(map(hex, self.repo.heads())) + "\n"
962 resp = " ".join(map(hex, self.repo.heads())) + "\n"
979 req.httphdr("application/mercurial-0.1", length=len(resp))
963 req.httphdr("application/mercurial-0.1", length=len(resp))
980 req.write(resp)
964 req.write(resp)
981
965
982 def do_branches(self, req):
966 def do_branches(self, req):
983 nodes = []
967 nodes = []
984 if req.form.has_key('nodes'):
968 if req.form.has_key('nodes'):
985 nodes = map(bin, req.form['nodes'][0].split(" "))
969 nodes = map(bin, req.form['nodes'][0].split(" "))
986 resp = cStringIO.StringIO()
970 resp = cStringIO.StringIO()
987 for b in self.repo.branches(nodes):
971 for b in self.repo.branches(nodes):
988 resp.write(" ".join(map(hex, b)) + "\n")
972 resp.write(" ".join(map(hex, b)) + "\n")
989 resp = resp.getvalue()
973 resp = resp.getvalue()
990 req.httphdr("application/mercurial-0.1", length=len(resp))
974 req.httphdr("application/mercurial-0.1", length=len(resp))
991 req.write(resp)
975 req.write(resp)
992
976
993 def do_between(self, req):
977 def do_between(self, req):
994 if req.form.has_key('pairs'):
978 if req.form.has_key('pairs'):
995 pairs = [map(bin, p.split("-"))
979 pairs = [map(bin, p.split("-"))
996 for p in req.form['pairs'][0].split(" ")]
980 for p in req.form['pairs'][0].split(" ")]
997 resp = cStringIO.StringIO()
981 resp = cStringIO.StringIO()
998 for b in self.repo.between(pairs):
982 for b in self.repo.between(pairs):
999 resp.write(" ".join(map(hex, b)) + "\n")
983 resp.write(" ".join(map(hex, b)) + "\n")
1000 resp = resp.getvalue()
984 resp = resp.getvalue()
1001 req.httphdr("application/mercurial-0.1", length=len(resp))
985 req.httphdr("application/mercurial-0.1", length=len(resp))
1002 req.write(resp)
986 req.write(resp)
1003
987
1004 def do_changegroup(self, req):
988 def do_changegroup(self, req):
1005 req.httphdr("application/mercurial-0.1")
989 req.httphdr("application/mercurial-0.1")
1006 nodes = []
990 nodes = []
1007 if not self.allowpull:
991 if not self.allowpull:
1008 return
992 return
1009
993
1010 if req.form.has_key('roots'):
994 if req.form.has_key('roots'):
1011 nodes = map(bin, req.form['roots'][0].split(" "))
995 nodes = map(bin, req.form['roots'][0].split(" "))
1012
996
1013 z = zlib.compressobj()
997 z = zlib.compressobj()
1014 f = self.repo.changegroup(nodes, 'serve')
998 f = self.repo.changegroup(nodes, 'serve')
1015 while 1:
999 while 1:
1016 chunk = f.read(4096)
1000 chunk = f.read(4096)
1017 if not chunk:
1001 if not chunk:
1018 break
1002 break
1019 req.write(z.compress(chunk))
1003 req.write(z.compress(chunk))
1020
1004
1021 req.write(z.flush())
1005 req.write(z.flush())
1022
1006
1023 def do_changegroupsubset(self, req):
1007 def do_changegroupsubset(self, req):
1024 req.httphdr("application/mercurial-0.1")
1008 req.httphdr("application/mercurial-0.1")
1025 bases = []
1009 bases = []
1026 heads = []
1010 heads = []
1027 if not self.allowpull:
1011 if not self.allowpull:
1028 return
1012 return
1029
1013
1030 if req.form.has_key('bases'):
1014 if req.form.has_key('bases'):
1031 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1015 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
1032 if req.form.has_key('heads'):
1016 if req.form.has_key('heads'):
1033 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1017 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
1034
1018
1035 z = zlib.compressobj()
1019 z = zlib.compressobj()
1036 f = self.repo.changegroupsubset(bases, heads, 'serve')
1020 f = self.repo.changegroupsubset(bases, heads, 'serve')
1037 while 1:
1021 while 1:
1038 chunk = f.read(4096)
1022 chunk = f.read(4096)
1039 if not chunk:
1023 if not chunk:
1040 break
1024 break
1041 req.write(z.compress(chunk))
1025 req.write(z.compress(chunk))
1042
1026
1043 req.write(z.flush())
1027 req.write(z.flush())
1044
1028
1045 def do_archive(self, req):
1029 def do_archive(self, req):
1046 type_ = req.form['type'][0]
1030 type_ = req.form['type'][0]
1047 allowed = self.configlist("web", "allow_archive")
1031 allowed = self.configlist("web", "allow_archive")
1048 if (type_ in self.archives and (type_ in allowed or
1032 if (type_ in self.archives and (type_ in allowed or
1049 self.configbool("web", "allow" + type_, False))):
1033 self.configbool("web", "allow" + type_, False))):
1050 self.archive(req, req.form['node'][0], type_)
1034 self.archive(req, req.form['node'][0], type_)
1051 return
1035 return
1052
1036
1053 req.respond(400, self.t('error',
1037 req.respond(400, self.t('error',
1054 error='Unsupported archive type: %s' % type_))
1038 error='Unsupported archive type: %s' % type_))
1055
1039
1056 def do_static(self, req):
1040 def do_static(self, req):
1057 fname = req.form['file'][0]
1041 fname = req.form['file'][0]
1058 # a repo owner may set web.static in .hg/hgrc to get any file
1042 # a repo owner may set web.static in .hg/hgrc to get any file
1059 # readable by the user running the CGI script
1043 # readable by the user running the CGI script
1060 static = self.config("web", "static",
1044 static = self.config("web", "static",
1061 os.path.join(self.templatepath, "static"),
1045 os.path.join(self.templatepath, "static"),
1062 untrusted=False)
1046 untrusted=False)
1063 req.write(staticfile(static, fname, req))
1047 req.write(staticfile(static, fname, req))
1064
1048
1065 def do_capabilities(self, req):
1049 def do_capabilities(self, req):
1066 caps = ['lookup', 'changegroupsubset']
1050 caps = ['lookup', 'changegroupsubset']
1067 if self.configbool('server', 'uncompressed'):
1051 if self.configbool('server', 'uncompressed'):
1068 caps.append('stream=%d' % self.repo.changelog.version)
1052 caps.append('stream=%d' % self.repo.changelog.version)
1069 # XXX: make configurable and/or share code with do_unbundle:
1053 # XXX: make configurable and/or share code with do_unbundle:
1070 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1054 unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
1071 if unbundleversions:
1055 if unbundleversions:
1072 caps.append('unbundle=%s' % ','.join(unbundleversions))
1056 caps.append('unbundle=%s' % ','.join(unbundleversions))
1073 resp = ' '.join(caps)
1057 resp = ' '.join(caps)
1074 req.httphdr("application/mercurial-0.1", length=len(resp))
1058 req.httphdr("application/mercurial-0.1", length=len(resp))
1075 req.write(resp)
1059 req.write(resp)
1076
1060
1077 def check_perm(self, req, op, default):
1061 def check_perm(self, req, op, default):
1078 '''check permission for operation based on user auth.
1062 '''check permission for operation based on user auth.
1079 return true if op allowed, else false.
1063 return true if op allowed, else false.
1080 default is policy to use if no config given.'''
1064 default is policy to use if no config given.'''
1081
1065
1082 user = req.env.get('REMOTE_USER')
1066 user = req.env.get('REMOTE_USER')
1083
1067
1084 deny = self.configlist('web', 'deny_' + op)
1068 deny = self.configlist('web', 'deny_' + op)
1085 if deny and (not user or deny == ['*'] or user in deny):
1069 if deny and (not user or deny == ['*'] or user in deny):
1086 return False
1070 return False
1087
1071
1088 allow = self.configlist('web', 'allow_' + op)
1072 allow = self.configlist('web', 'allow_' + op)
1089 return (allow and (allow == ['*'] or user in allow)) or default
1073 return (allow and (allow == ['*'] or user in allow)) or default
1090
1074
1091 def do_unbundle(self, req):
1075 def do_unbundle(self, req):
1092 def bail(response, headers={}):
1076 def bail(response, headers={}):
1093 length = int(req.env['CONTENT_LENGTH'])
1077 length = int(req.env['CONTENT_LENGTH'])
1094 for s in util.filechunkiter(req, limit=length):
1078 for s in util.filechunkiter(req, limit=length):
1095 # drain incoming bundle, else client will not see
1079 # drain incoming bundle, else client will not see
1096 # response when run outside cgi script
1080 # response when run outside cgi script
1097 pass
1081 pass
1098 req.httphdr("application/mercurial-0.1", headers=headers)
1082 req.httphdr("application/mercurial-0.1", headers=headers)
1099 req.write('0\n')
1083 req.write('0\n')
1100 req.write(response)
1084 req.write(response)
1101
1085
1102 # require ssl by default, auth info cannot be sniffed and
1086 # require ssl by default, auth info cannot be sniffed and
1103 # replayed
1087 # replayed
1104 ssl_req = self.configbool('web', 'push_ssl', True)
1088 ssl_req = self.configbool('web', 'push_ssl', True)
1105 if ssl_req:
1089 if ssl_req:
1106 if req.env.get('wsgi.url_scheme') != 'https':
1090 if req.env.get('wsgi.url_scheme') != 'https':
1107 bail(_('ssl required\n'))
1091 bail(_('ssl required\n'))
1108 return
1092 return
1109 proto = 'https'
1093 proto = 'https'
1110 else:
1094 else:
1111 proto = 'http'
1095 proto = 'http'
1112
1096
1113 # do not allow push unless explicitly allowed
1097 # do not allow push unless explicitly allowed
1114 if not self.check_perm(req, 'push', False):
1098 if not self.check_perm(req, 'push', False):
1115 bail(_('push not authorized\n'),
1099 bail(_('push not authorized\n'),
1116 headers={'status': '401 Unauthorized'})
1100 headers={'status': '401 Unauthorized'})
1117 return
1101 return
1118
1102
1119 their_heads = req.form['heads'][0].split(' ')
1103 their_heads = req.form['heads'][0].split(' ')
1120
1104
1121 def check_heads():
1105 def check_heads():
1122 heads = map(hex, self.repo.heads())
1106 heads = map(hex, self.repo.heads())
1123 return their_heads == [hex('force')] or their_heads == heads
1107 return their_heads == [hex('force')] or their_heads == heads
1124
1108
1125 # fail early if possible
1109 # fail early if possible
1126 if not check_heads():
1110 if not check_heads():
1127 bail(_('unsynced changes\n'))
1111 bail(_('unsynced changes\n'))
1128 return
1112 return
1129
1113
1130 req.httphdr("application/mercurial-0.1")
1114 req.httphdr("application/mercurial-0.1")
1131
1115
1132 # do not lock repo until all changegroup data is
1116 # do not lock repo until all changegroup data is
1133 # streamed. save to temporary file.
1117 # streamed. save to temporary file.
1134
1118
1135 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1119 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1136 fp = os.fdopen(fd, 'wb+')
1120 fp = os.fdopen(fd, 'wb+')
1137 try:
1121 try:
1138 length = int(req.env['CONTENT_LENGTH'])
1122 length = int(req.env['CONTENT_LENGTH'])
1139 for s in util.filechunkiter(req, limit=length):
1123 for s in util.filechunkiter(req, limit=length):
1140 fp.write(s)
1124 fp.write(s)
1141
1125
1142 try:
1126 try:
1143 lock = self.repo.lock()
1127 lock = self.repo.lock()
1144 try:
1128 try:
1145 if not check_heads():
1129 if not check_heads():
1146 req.write('0\n')
1130 req.write('0\n')
1147 req.write(_('unsynced changes\n'))
1131 req.write(_('unsynced changes\n'))
1148 return
1132 return
1149
1133
1150 fp.seek(0)
1134 fp.seek(0)
1151 header = fp.read(6)
1135 header = fp.read(6)
1152 if not header.startswith("HG"):
1136 if not header.startswith("HG"):
1153 # old client with uncompressed bundle
1137 # old client with uncompressed bundle
1154 def generator(f):
1138 def generator(f):
1155 yield header
1139 yield header
1156 for chunk in f:
1140 for chunk in f:
1157 yield chunk
1141 yield chunk
1158 elif not header.startswith("HG10"):
1142 elif not header.startswith("HG10"):
1159 req.write("0\n")
1143 req.write("0\n")
1160 req.write(_("unknown bundle version\n"))
1144 req.write(_("unknown bundle version\n"))
1161 return
1145 return
1162 elif header == "HG10GZ":
1146 elif header == "HG10GZ":
1163 def generator(f):
1147 def generator(f):
1164 zd = zlib.decompressobj()
1148 zd = zlib.decompressobj()
1165 for chunk in f:
1149 for chunk in f:
1166 yield zd.decompress(chunk)
1150 yield zd.decompress(chunk)
1167 elif header == "HG10BZ":
1151 elif header == "HG10BZ":
1168 def generator(f):
1152 def generator(f):
1169 zd = bz2.BZ2Decompressor()
1153 zd = bz2.BZ2Decompressor()
1170 zd.decompress("BZ")
1154 zd.decompress("BZ")
1171 for chunk in f:
1155 for chunk in f:
1172 yield zd.decompress(chunk)
1156 yield zd.decompress(chunk)
1173 elif header == "HG10UN":
1157 elif header == "HG10UN":
1174 def generator(f):
1158 def generator(f):
1175 for chunk in f:
1159 for chunk in f:
1176 yield chunk
1160 yield chunk
1177 else:
1161 else:
1178 req.write("0\n")
1162 req.write("0\n")
1179 req.write(_("unknown bundle compression type\n"))
1163 req.write(_("unknown bundle compression type\n"))
1180 return
1164 return
1181 gen = generator(util.filechunkiter(fp, 4096))
1165 gen = generator(util.filechunkiter(fp, 4096))
1182
1166
1183 # send addchangegroup output to client
1167 # send addchangegroup output to client
1184
1168
1185 old_stdout = sys.stdout
1169 old_stdout = sys.stdout
1186 sys.stdout = cStringIO.StringIO()
1170 sys.stdout = cStringIO.StringIO()
1187
1171
1188 try:
1172 try:
1189 url = 'remote:%s:%s' % (proto,
1173 url = 'remote:%s:%s' % (proto,
1190 req.env.get('REMOTE_HOST', ''))
1174 req.env.get('REMOTE_HOST', ''))
1191 try:
1175 try:
1192 ret = self.repo.addchangegroup(
1176 ret = self.repo.addchangegroup(
1193 util.chunkbuffer(gen), 'serve', url)
1177 util.chunkbuffer(gen), 'serve', url)
1194 except util.Abort, inst:
1178 except util.Abort, inst:
1195 sys.stdout.write("abort: %s\n" % inst)
1179 sys.stdout.write("abort: %s\n" % inst)
1196 ret = 0
1180 ret = 0
1197 finally:
1181 finally:
1198 val = sys.stdout.getvalue()
1182 val = sys.stdout.getvalue()
1199 sys.stdout = old_stdout
1183 sys.stdout = old_stdout
1200 req.write('%d\n' % ret)
1184 req.write('%d\n' % ret)
1201 req.write(val)
1185 req.write(val)
1202 finally:
1186 finally:
1203 del lock
1187 del lock
1204 except (OSError, IOError), inst:
1188 except (OSError, IOError), inst:
1205 req.write('0\n')
1189 req.write('0\n')
1206 filename = getattr(inst, 'filename', '')
1190 filename = getattr(inst, 'filename', '')
1207 # Don't send our filesystem layout to the client
1191 # Don't send our filesystem layout to the client
1208 if filename.startswith(self.repo.root):
1192 if filename.startswith(self.repo.root):
1209 filename = filename[len(self.repo.root)+1:]
1193 filename = filename[len(self.repo.root)+1:]
1210 else:
1194 else:
1211 filename = ''
1195 filename = ''
1212 error = getattr(inst, 'strerror', 'Unknown error')
1196 error = getattr(inst, 'strerror', 'Unknown error')
1213 if inst.errno == errno.ENOENT:
1197 if inst.errno == errno.ENOENT:
1214 code = 404
1198 code = 404
1215 else:
1199 else:
1216 code = 500
1200 code = 500
1217 req.respond(code, '%s: %s\n' % (error, filename))
1201 req.respond(code, '%s: %s\n' % (error, filename))
1218 finally:
1202 finally:
1219 fp.close()
1203 fp.close()
1220 os.unlink(tempname)
1204 os.unlink(tempname)
1221
1205
1222 def do_stream_out(self, req):
1206 def do_stream_out(self, req):
1223 req.httphdr("application/mercurial-0.1")
1207 req.httphdr("application/mercurial-0.1")
1224 streamclone.stream_out(self.repo, req, untrusted=True)
1208 streamclone.stream_out(self.repo, req, untrusted=True)
@@ -1,264 +1,262 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetools, cStringIO
9 import os, mimetools, cStringIO
10 from mercurial.i18n import gettext as _
10 from mercurial.i18n import gettext as _
11 from mercurial import ui, hg, util, templater
11 from mercurial import ui, hg, util, templater
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
12 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
13 from hgweb_mod import hgweb
13 from hgweb_mod import hgweb
14 from request import wsgirequest
14 from request import wsgirequest
15
15
16 # This is a stopgap
16 # This is a stopgap
17 class hgwebdir(object):
17 class hgwebdir(object):
18 def __init__(self, config, parentui=None):
18 def __init__(self, config, parentui=None):
19 def cleannames(items):
19 def cleannames(items):
20 return [(util.pconvert(name.strip(os.sep)), path)
20 return [(util.pconvert(name.strip(os.sep)), path)
21 for name, path in items]
21 for name, path in items]
22
22
23 self.parentui = parentui
23 self.parentui = parentui
24 self.motd = None
24 self.motd = None
25 self.style = None
25 self.style = None
26 self.stripecount = None
26 self.stripecount = None
27 self.repos_sorted = ('name', False)
27 self.repos_sorted = ('name', False)
28 if isinstance(config, (list, tuple)):
28 if isinstance(config, (list, tuple)):
29 self.repos = cleannames(config)
29 self.repos = cleannames(config)
30 self.repos_sorted = ('', False)
30 self.repos_sorted = ('', False)
31 elif isinstance(config, dict):
31 elif isinstance(config, dict):
32 self.repos = cleannames(config.items())
32 self.repos = cleannames(config.items())
33 self.repos.sort()
33 self.repos.sort()
34 else:
34 else:
35 if isinstance(config, util.configparser):
35 if isinstance(config, util.configparser):
36 cp = config
36 cp = config
37 else:
37 else:
38 cp = util.configparser()
38 cp = util.configparser()
39 cp.read(config)
39 cp.read(config)
40 self.repos = []
40 self.repos = []
41 if cp.has_section('web'):
41 if cp.has_section('web'):
42 if cp.has_option('web', 'motd'):
42 if cp.has_option('web', 'motd'):
43 self.motd = cp.get('web', 'motd')
43 self.motd = cp.get('web', 'motd')
44 if cp.has_option('web', 'style'):
44 if cp.has_option('web', 'style'):
45 self.style = cp.get('web', 'style')
45 self.style = cp.get('web', 'style')
46 if cp.has_option('web', 'stripes'):
46 if cp.has_option('web', 'stripes'):
47 self.stripecount = int(cp.get('web', 'stripes'))
47 self.stripecount = int(cp.get('web', 'stripes'))
48 if cp.has_section('paths'):
48 if cp.has_section('paths'):
49 self.repos.extend(cleannames(cp.items('paths')))
49 self.repos.extend(cleannames(cp.items('paths')))
50 if cp.has_section('collections'):
50 if cp.has_section('collections'):
51 for prefix, root in cp.items('collections'):
51 for prefix, root in cp.items('collections'):
52 for path in util.walkrepos(root):
52 for path in util.walkrepos(root):
53 repo = os.path.normpath(path)
53 repo = os.path.normpath(path)
54 name = repo
54 name = repo
55 if name.startswith(prefix):
55 if name.startswith(prefix):
56 name = name[len(prefix):]
56 name = name[len(prefix):]
57 self.repos.append((name.lstrip(os.sep), repo))
57 self.repos.append((name.lstrip(os.sep), repo))
58 self.repos.sort()
58 self.repos.sort()
59
59
60 def run(self):
60 def run(self):
61 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
61 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
62 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
62 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
63 import mercurial.hgweb.wsgicgi as wsgicgi
63 import mercurial.hgweb.wsgicgi as wsgicgi
64 wsgicgi.launch(self)
64 wsgicgi.launch(self)
65
65
66 def __call__(self, env, respond):
66 def __call__(self, env, respond):
67 req = wsgirequest(env, respond)
67 req = wsgirequest(env, respond)
68 self.run_wsgi(req)
68 self.run_wsgi(req)
69 return req
69 return req
70
70
71 def run_wsgi(self, req):
71 def run_wsgi(self, req):
72 def header(**map):
72 def header(**map):
73 header_file = cStringIO.StringIO(
73 header_file = cStringIO.StringIO(
74 ''.join(tmpl("header", encoding=util._encoding, **map)))
74 ''.join(tmpl("header", encoding=util._encoding, **map)))
75 msg = mimetools.Message(header_file, 0)
75 msg = mimetools.Message(header_file, 0)
76 req.header(msg.items())
76 req.header(msg.items())
77 yield header_file.read()
77 yield header_file.read()
78
78
79 def footer(**map):
79 def footer(**map):
80 yield tmpl("footer", **map)
80 yield tmpl("footer", **map)
81
81
82 def motd(**map):
82 def motd(**map):
83 if self.motd is not None:
83 if self.motd is not None:
84 yield self.motd
84 yield self.motd
85 else:
85 else:
86 yield config('web', 'motd', '')
86 yield config('web', 'motd', '')
87
87
88 parentui = self.parentui or ui.ui(report_untrusted=False,
88 parentui = self.parentui or ui.ui(report_untrusted=False,
89 interactive=False)
89 interactive=False)
90
90
91 def config(section, name, default=None, untrusted=True):
91 def config(section, name, default=None, untrusted=True):
92 return parentui.config(section, name, default, untrusted)
92 return parentui.config(section, name, default, untrusted)
93
93
94 url = req.env['REQUEST_URI'].split('?')[0]
94 url = req.env.get('SCRIPT_NAME', '')
95 if not url.endswith('/'):
95 if not url.endswith('/'):
96 url += '/'
96 url += '/'
97 pathinfo = req.env.get('PATH_INFO', '').strip('/') + '/'
98 base = url[:len(url) - len(pathinfo)]
99 if not base.endswith('/'):
100 base += '/'
101
97
102 staticurl = config('web', 'staticurl') or base + 'static/'
98 staticurl = config('web', 'staticurl') or url + 'static/'
103 if not staticurl.endswith('/'):
99 if not staticurl.endswith('/'):
104 staticurl += '/'
100 staticurl += '/'
105
101
106 style = self.style
102 style = self.style
107 if style is None:
103 if style is None:
108 style = config('web', 'style', '')
104 style = config('web', 'style', '')
109 if req.form.has_key('style'):
105 if req.form.has_key('style'):
110 style = req.form['style'][0]
106 style = req.form['style'][0]
111 if self.stripecount is None:
107 if self.stripecount is None:
112 self.stripecount = int(config('web', 'stripes', 1))
108 self.stripecount = int(config('web', 'stripes', 1))
113 mapfile = style_map(templater.templatepath(), style)
109 mapfile = style_map(templater.templatepath(), style)
114 tmpl = templater.templater(mapfile, templater.common_filters,
110 tmpl = templater.templater(mapfile, templater.common_filters,
115 defaults={"header": header,
111 defaults={"header": header,
116 "footer": footer,
112 "footer": footer,
117 "motd": motd,
113 "motd": motd,
118 "url": url,
114 "url": url,
119 "staticurl": staticurl})
115 "staticurl": staticurl})
120
116
121 def archivelist(ui, nodeid, url):
117 def archivelist(ui, nodeid, url):
122 allowed = ui.configlist("web", "allow_archive", untrusted=True)
118 allowed = ui.configlist("web", "allow_archive", untrusted=True)
123 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
119 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
124 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
120 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
125 untrusted=True):
121 untrusted=True):
126 yield {"type" : i[0], "extension": i[1],
122 yield {"type" : i[0], "extension": i[1],
127 "node": nodeid, "url": url}
123 "node": nodeid, "url": url}
128
124
129 def entries(sortcolumn="", descending=False, subdir="", **map):
125 def entries(sortcolumn="", descending=False, subdir="", **map):
130 def sessionvars(**map):
126 def sessionvars(**map):
131 fields = []
127 fields = []
132 if req.form.has_key('style'):
128 if req.form.has_key('style'):
133 style = req.form['style'][0]
129 style = req.form['style'][0]
134 if style != get('web', 'style', ''):
130 if style != get('web', 'style', ''):
135 fields.append(('style', style))
131 fields.append(('style', style))
136
132
137 separator = url[-1] == '?' and ';' or '?'
133 separator = url[-1] == '?' and ';' or '?'
138 for name, value in fields:
134 for name, value in fields:
139 yield dict(name=name, value=value, separator=separator)
135 yield dict(name=name, value=value, separator=separator)
140 separator = ';'
136 separator = ';'
141
137
142 rows = []
138 rows = []
143 parity = paritygen(self.stripecount)
139 parity = paritygen(self.stripecount)
144 for name, path in self.repos:
140 for name, path in self.repos:
145 if not name.startswith(subdir):
141 if not name.startswith(subdir):
146 continue
142 continue
147 name = name[len(subdir):]
143 name = name[len(subdir):]
148
144
149 u = ui.ui(parentui=parentui)
145 u = ui.ui(parentui=parentui)
150 try:
146 try:
151 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
147 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
152 except Exception, e:
148 except Exception, e:
153 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
149 u.warn(_('error reading %s/.hg/hgrc: %s\n' % (path, e)))
154 continue
150 continue
155 def get(section, name, default=None):
151 def get(section, name, default=None):
156 return u.config(section, name, default, untrusted=True)
152 return u.config(section, name, default, untrusted=True)
157
153
158 if u.configbool("web", "hidden", untrusted=True):
154 if u.configbool("web", "hidden", untrusted=True):
159 continue
155 continue
160
156
161 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
157 parts = [req.env['PATH_INFO'], name]
162 .replace("//", "/")) + '/'
158 if req.env['SCRIPT_NAME']:
159 parts.insert(0, req.env['SCRIPT_NAME'])
160 url = ('/'.join(parts).replace("//", "/")) + '/'
163
161
164 # update time with local timezone
162 # update time with local timezone
165 try:
163 try:
166 d = (get_mtime(path), util.makedate()[1])
164 d = (get_mtime(path), util.makedate()[1])
167 except OSError:
165 except OSError:
168 continue
166 continue
169
167
170 contact = (get("ui", "username") or # preferred
168 contact = (get("ui", "username") or # preferred
171 get("web", "contact") or # deprecated
169 get("web", "contact") or # deprecated
172 get("web", "author", "")) # also
170 get("web", "author", "")) # also
173 description = get("web", "description", "")
171 description = get("web", "description", "")
174 name = get("web", "name", name)
172 name = get("web", "name", name)
175 row = dict(contact=contact or "unknown",
173 row = dict(contact=contact or "unknown",
176 contact_sort=contact.upper() or "unknown",
174 contact_sort=contact.upper() or "unknown",
177 name=name,
175 name=name,
178 name_sort=name,
176 name_sort=name,
179 url=url,
177 url=url,
180 description=description or "unknown",
178 description=description or "unknown",
181 description_sort=description.upper() or "unknown",
179 description_sort=description.upper() or "unknown",
182 lastchange=d,
180 lastchange=d,
183 lastchange_sort=d[1]-d[0],
181 lastchange_sort=d[1]-d[0],
184 sessionvars=sessionvars,
182 sessionvars=sessionvars,
185 archives=archivelist(u, "tip", url))
183 archives=archivelist(u, "tip", url))
186 if (not sortcolumn
184 if (not sortcolumn
187 or (sortcolumn, descending) == self.repos_sorted):
185 or (sortcolumn, descending) == self.repos_sorted):
188 # fast path for unsorted output
186 # fast path for unsorted output
189 row['parity'] = parity.next()
187 row['parity'] = parity.next()
190 yield row
188 yield row
191 else:
189 else:
192 rows.append((row["%s_sort" % sortcolumn], row))
190 rows.append((row["%s_sort" % sortcolumn], row))
193 if rows:
191 if rows:
194 rows.sort()
192 rows.sort()
195 if descending:
193 if descending:
196 rows.reverse()
194 rows.reverse()
197 for key, row in rows:
195 for key, row in rows:
198 row['parity'] = parity.next()
196 row['parity'] = parity.next()
199 yield row
197 yield row
200
198
201 def makeindex(req, subdir=""):
199 def makeindex(req, subdir=""):
202 sortable = ["name", "description", "contact", "lastchange"]
200 sortable = ["name", "description", "contact", "lastchange"]
203 sortcolumn, descending = self.repos_sorted
201 sortcolumn, descending = self.repos_sorted
204 if req.form.has_key('sort'):
202 if req.form.has_key('sort'):
205 sortcolumn = req.form['sort'][0]
203 sortcolumn = req.form['sort'][0]
206 descending = sortcolumn.startswith('-')
204 descending = sortcolumn.startswith('-')
207 if descending:
205 if descending:
208 sortcolumn = sortcolumn[1:]
206 sortcolumn = sortcolumn[1:]
209 if sortcolumn not in sortable:
207 if sortcolumn not in sortable:
210 sortcolumn = ""
208 sortcolumn = ""
211
209
212 sort = [("sort_%s" % column,
210 sort = [("sort_%s" % column,
213 "%s%s" % ((not descending and column == sortcolumn)
211 "%s%s" % ((not descending and column == sortcolumn)
214 and "-" or "", column))
212 and "-" or "", column))
215 for column in sortable]
213 for column in sortable]
216 req.write(tmpl("index", entries=entries, subdir=subdir,
214 req.write(tmpl("index", entries=entries, subdir=subdir,
217 sortcolumn=sortcolumn, descending=descending,
215 sortcolumn=sortcolumn, descending=descending,
218 **dict(sort)))
216 **dict(sort)))
219
217
220 try:
218 try:
221 try:
219 try:
222 virtual = req.env.get("PATH_INFO", "").strip('/')
220 virtual = req.env.get("PATH_INFO", "").strip('/')
223 if virtual.startswith('static/'):
221 if virtual.startswith('static/'):
224 static = os.path.join(templater.templatepath(), 'static')
222 static = os.path.join(templater.templatepath(), 'static')
225 fname = virtual[7:]
223 fname = virtual[7:]
226 req.write(staticfile(static, fname, req))
224 req.write(staticfile(static, fname, req))
227 elif virtual:
225 elif virtual:
228 repos = dict(self.repos)
226 repos = dict(self.repos)
229 while virtual:
227 while virtual:
230 real = repos.get(virtual)
228 real = repos.get(virtual)
231 if real:
229 if real:
232 req.env['REPO_NAME'] = virtual
230 req.env['REPO_NAME'] = virtual
233 try:
231 try:
234 repo = hg.repository(parentui, real)
232 repo = hg.repository(parentui, real)
235 hgweb(repo).run_wsgi(req)
233 hgweb(repo).run_wsgi(req)
236 return
234 return
237 except IOError, inst:
235 except IOError, inst:
238 raise ErrorResponse(500, inst.strerror)
236 raise ErrorResponse(500, inst.strerror)
239 except hg.RepoError, inst:
237 except hg.RepoError, inst:
240 raise ErrorResponse(500, str(inst))
238 raise ErrorResponse(500, str(inst))
241
239
242 # browse subdirectories
240 # browse subdirectories
243 subdir = virtual + '/'
241 subdir = virtual + '/'
244 if [r for r in repos if r.startswith(subdir)]:
242 if [r for r in repos if r.startswith(subdir)]:
245 makeindex(req, subdir)
243 makeindex(req, subdir)
246 return
244 return
247
245
248 up = virtual.rfind('/')
246 up = virtual.rfind('/')
249 if up < 0:
247 if up < 0:
250 break
248 break
251 virtual = virtual[:up]
249 virtual = virtual[:up]
252
250
253 req.respond(404, tmpl("notfound", repo=virtual))
251 req.respond(404, tmpl("notfound", repo=virtual))
254 else:
252 else:
255 if req.form.has_key('static'):
253 if req.form.has_key('static'):
256 static = os.path.join(templater.templatepath(), "static")
254 static = os.path.join(templater.templatepath(), "static")
257 fname = req.form['static'][0]
255 fname = req.form['static'][0]
258 req.write(staticfile(static, fname, req))
256 req.write(staticfile(static, fname, req))
259 else:
257 else:
260 makeindex(req)
258 makeindex(req)
261 except ErrorResponse, err:
259 except ErrorResponse, err:
262 req.respond(err.code, tmpl('error', error=err.message or ''))
260 req.respond(err.code, tmpl('error', error=err.message or ''))
263 finally:
261 finally:
264 tmpl = None
262 tmpl = None
@@ -1,289 +1,290 b''
1 # hgweb/server.py - The standalone hg web server.
1 # hgweb/server.py - The standalone hg web 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-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, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
10 from mercurial import ui, hg, util, templater
10 from mercurial import ui, hg, util, templater
11 from hgweb_mod import hgweb
11 from hgweb_mod import hgweb
12 from hgwebdir_mod import hgwebdir
12 from hgwebdir_mod import hgwebdir
13 from mercurial.i18n import gettext as _
13 from mercurial.i18n import gettext as _
14
14
15 def _splitURI(uri):
15 def _splitURI(uri):
16 """ Return path and query splited from uri
16 """ Return path and query splited from uri
17
17
18 Just like CGI environment, the path is unquoted, the query is
18 Just like CGI environment, the path is unquoted, the query is
19 not.
19 not.
20 """
20 """
21 if '?' in uri:
21 if '?' in uri:
22 path, query = uri.split('?', 1)
22 path, query = uri.split('?', 1)
23 else:
23 else:
24 path, query = uri, ''
24 path, query = uri, ''
25 return urllib.unquote(path), query
25 return urllib.unquote(path), query
26
26
27 class _error_logger(object):
27 class _error_logger(object):
28 def __init__(self, handler):
28 def __init__(self, handler):
29 self.handler = handler
29 self.handler = handler
30 def flush(self):
30 def flush(self):
31 pass
31 pass
32 def write(self, str):
32 def write(self, str):
33 self.writelines(str.split('\n'))
33 self.writelines(str.split('\n'))
34 def writelines(self, seq):
34 def writelines(self, seq):
35 for msg in seq:
35 for msg in seq:
36 self.handler.log_error("HG error: %s", msg)
36 self.handler.log_error("HG error: %s", msg)
37
37
38 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
38 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
39
39
40 url_scheme = 'http'
40 url_scheme = 'http'
41
41
42 def __init__(self, *args, **kargs):
42 def __init__(self, *args, **kargs):
43 self.protocol_version = 'HTTP/1.1'
43 self.protocol_version = 'HTTP/1.1'
44 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
44 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
45
45
46 def _log_any(self, fp, format, *args):
46 def _log_any(self, fp, format, *args):
47 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
47 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
48 self.log_date_time_string(),
48 self.log_date_time_string(),
49 format % args))
49 format % args))
50 fp.flush()
50 fp.flush()
51
51
52 def log_error(self, format, *args):
52 def log_error(self, format, *args):
53 self._log_any(self.server.errorlog, format, *args)
53 self._log_any(self.server.errorlog, format, *args)
54
54
55 def log_message(self, format, *args):
55 def log_message(self, format, *args):
56 self._log_any(self.server.accesslog, format, *args)
56 self._log_any(self.server.accesslog, format, *args)
57
57
58 def do_write(self):
58 def do_write(self):
59 try:
59 try:
60 self.do_hgweb()
60 self.do_hgweb()
61 except socket.error, inst:
61 except socket.error, inst:
62 if inst[0] != errno.EPIPE:
62 if inst[0] != errno.EPIPE:
63 raise
63 raise
64
64
65 def do_POST(self):
65 def do_POST(self):
66 try:
66 try:
67 self.do_write()
67 self.do_write()
68 except StandardError, inst:
68 except StandardError, inst:
69 self._start_response("500 Internal Server Error", [])
69 self._start_response("500 Internal Server Error", [])
70 self._write("Internal Server Error")
70 self._write("Internal Server Error")
71 tb = "".join(traceback.format_exception(*sys.exc_info()))
71 tb = "".join(traceback.format_exception(*sys.exc_info()))
72 self.log_error("Exception happened during processing request '%s':\n%s",
72 self.log_error("Exception happened during processing request '%s':\n%s",
73 self.path, tb)
73 self.path, tb)
74
74
75 def do_GET(self):
75 def do_GET(self):
76 self.do_POST()
76 self.do_POST()
77
77
78 def do_hgweb(self):
78 def do_hgweb(self):
79 path_info, query = _splitURI(self.path)
79 path_info, query = _splitURI(self.path)
80
80
81 env = {}
81 env = {}
82 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
82 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
83 env['REQUEST_METHOD'] = self.command
83 env['REQUEST_METHOD'] = self.command
84 env['SERVER_NAME'] = self.server.server_name
84 env['SERVER_NAME'] = self.server.server_name
85 env['SERVER_PORT'] = str(self.server.server_port)
85 env['SERVER_PORT'] = str(self.server.server_port)
86 env['REQUEST_URI'] = self.path
86 env['REQUEST_URI'] = self.path
87 env['SCRIPT_NAME'] = ''
87 env['PATH_INFO'] = path_info
88 env['PATH_INFO'] = path_info
88 env['REMOTE_HOST'] = self.client_address[0]
89 env['REMOTE_HOST'] = self.client_address[0]
89 env['REMOTE_ADDR'] = self.client_address[0]
90 env['REMOTE_ADDR'] = self.client_address[0]
90 if query:
91 if query:
91 env['QUERY_STRING'] = query
92 env['QUERY_STRING'] = query
92
93
93 if self.headers.typeheader is None:
94 if self.headers.typeheader is None:
94 env['CONTENT_TYPE'] = self.headers.type
95 env['CONTENT_TYPE'] = self.headers.type
95 else:
96 else:
96 env['CONTENT_TYPE'] = self.headers.typeheader
97 env['CONTENT_TYPE'] = self.headers.typeheader
97 length = self.headers.getheader('content-length')
98 length = self.headers.getheader('content-length')
98 if length:
99 if length:
99 env['CONTENT_LENGTH'] = length
100 env['CONTENT_LENGTH'] = length
100 for header in [h for h in self.headers.keys()
101 for header in [h for h in self.headers.keys()
101 if h not in ('content-type', 'content-length')]:
102 if h not in ('content-type', 'content-length')]:
102 hkey = 'HTTP_' + header.replace('-', '_').upper()
103 hkey = 'HTTP_' + header.replace('-', '_').upper()
103 hval = self.headers.getheader(header)
104 hval = self.headers.getheader(header)
104 hval = hval.replace('\n', '').strip()
105 hval = hval.replace('\n', '').strip()
105 if hval:
106 if hval:
106 env[hkey] = hval
107 env[hkey] = hval
107 env['SERVER_PROTOCOL'] = self.request_version
108 env['SERVER_PROTOCOL'] = self.request_version
108 env['wsgi.version'] = (1, 0)
109 env['wsgi.version'] = (1, 0)
109 env['wsgi.url_scheme'] = self.url_scheme
110 env['wsgi.url_scheme'] = self.url_scheme
110 env['wsgi.input'] = self.rfile
111 env['wsgi.input'] = self.rfile
111 env['wsgi.errors'] = _error_logger(self)
112 env['wsgi.errors'] = _error_logger(self)
112 env['wsgi.multithread'] = isinstance(self.server,
113 env['wsgi.multithread'] = isinstance(self.server,
113 SocketServer.ThreadingMixIn)
114 SocketServer.ThreadingMixIn)
114 env['wsgi.multiprocess'] = isinstance(self.server,
115 env['wsgi.multiprocess'] = isinstance(self.server,
115 SocketServer.ForkingMixIn)
116 SocketServer.ForkingMixIn)
116 env['wsgi.run_once'] = 0
117 env['wsgi.run_once'] = 0
117
118
118 self.close_connection = True
119 self.close_connection = True
119 self.saved_status = None
120 self.saved_status = None
120 self.saved_headers = []
121 self.saved_headers = []
121 self.sent_headers = False
122 self.sent_headers = False
122 self.length = None
123 self.length = None
123 self.server.application(env, self._start_response)
124 self.server.application(env, self._start_response)
124
125
125 def send_headers(self):
126 def send_headers(self):
126 if not self.saved_status:
127 if not self.saved_status:
127 raise AssertionError("Sending headers before start_response() called")
128 raise AssertionError("Sending headers before start_response() called")
128 saved_status = self.saved_status.split(None, 1)
129 saved_status = self.saved_status.split(None, 1)
129 saved_status[0] = int(saved_status[0])
130 saved_status[0] = int(saved_status[0])
130 self.send_response(*saved_status)
131 self.send_response(*saved_status)
131 should_close = True
132 should_close = True
132 for h in self.saved_headers:
133 for h in self.saved_headers:
133 self.send_header(*h)
134 self.send_header(*h)
134 if h[0].lower() == 'content-length':
135 if h[0].lower() == 'content-length':
135 should_close = False
136 should_close = False
136 self.length = int(h[1])
137 self.length = int(h[1])
137 # The value of the Connection header is a list of case-insensitive
138 # The value of the Connection header is a list of case-insensitive
138 # tokens separated by commas and optional whitespace.
139 # tokens separated by commas and optional whitespace.
139 if 'close' in [token.strip().lower() for token in
140 if 'close' in [token.strip().lower() for token in
140 self.headers.get('connection', '').split(',')]:
141 self.headers.get('connection', '').split(',')]:
141 should_close = True
142 should_close = True
142 if should_close:
143 if should_close:
143 self.send_header('Connection', 'close')
144 self.send_header('Connection', 'close')
144 self.close_connection = should_close
145 self.close_connection = should_close
145 self.end_headers()
146 self.end_headers()
146 self.sent_headers = True
147 self.sent_headers = True
147
148
148 def _start_response(self, http_status, headers, exc_info=None):
149 def _start_response(self, http_status, headers, exc_info=None):
149 code, msg = http_status.split(None, 1)
150 code, msg = http_status.split(None, 1)
150 code = int(code)
151 code = int(code)
151 self.saved_status = http_status
152 self.saved_status = http_status
152 bad_headers = ('connection', 'transfer-encoding')
153 bad_headers = ('connection', 'transfer-encoding')
153 self.saved_headers = [h for h in headers
154 self.saved_headers = [h for h in headers
154 if h[0].lower() not in bad_headers]
155 if h[0].lower() not in bad_headers]
155 return self._write
156 return self._write
156
157
157 def _write(self, data):
158 def _write(self, data):
158 if not self.saved_status:
159 if not self.saved_status:
159 raise AssertionError("data written before start_response() called")
160 raise AssertionError("data written before start_response() called")
160 elif not self.sent_headers:
161 elif not self.sent_headers:
161 self.send_headers()
162 self.send_headers()
162 if self.length is not None:
163 if self.length is not None:
163 if len(data) > self.length:
164 if len(data) > self.length:
164 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
165 raise AssertionError("Content-length header sent, but more bytes than specified are being written.")
165 self.length = self.length - len(data)
166 self.length = self.length - len(data)
166 self.wfile.write(data)
167 self.wfile.write(data)
167 self.wfile.flush()
168 self.wfile.flush()
168
169
169 class _shgwebhandler(_hgwebhandler):
170 class _shgwebhandler(_hgwebhandler):
170
171
171 url_scheme = 'https'
172 url_scheme = 'https'
172
173
173 def setup(self):
174 def setup(self):
174 self.connection = self.request
175 self.connection = self.request
175 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
176 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
176 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
177 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
177
178
178 def do_write(self):
179 def do_write(self):
179 from OpenSSL.SSL import SysCallError
180 from OpenSSL.SSL import SysCallError
180 try:
181 try:
181 super(_shgwebhandler, self).do_write()
182 super(_shgwebhandler, self).do_write()
182 except SysCallError, inst:
183 except SysCallError, inst:
183 if inst.args[0] != errno.EPIPE:
184 if inst.args[0] != errno.EPIPE:
184 raise
185 raise
185
186
186 def handle_one_request(self):
187 def handle_one_request(self):
187 from OpenSSL.SSL import SysCallError, ZeroReturnError
188 from OpenSSL.SSL import SysCallError, ZeroReturnError
188 try:
189 try:
189 super(_shgwebhandler, self).handle_one_request()
190 super(_shgwebhandler, self).handle_one_request()
190 except (SysCallError, ZeroReturnError):
191 except (SysCallError, ZeroReturnError):
191 self.close_connection = True
192 self.close_connection = True
192 pass
193 pass
193
194
194 def create_server(ui, repo):
195 def create_server(ui, repo):
195 use_threads = True
196 use_threads = True
196
197
197 def openlog(opt, default):
198 def openlog(opt, default):
198 if opt and opt != '-':
199 if opt and opt != '-':
199 return open(opt, 'w')
200 return open(opt, 'w')
200 return default
201 return default
201
202
202 if repo is None:
203 if repo is None:
203 myui = ui
204 myui = ui
204 else:
205 else:
205 myui = repo.ui
206 myui = repo.ui
206 address = myui.config("web", "address", "")
207 address = myui.config("web", "address", "")
207 port = int(myui.config("web", "port", 8000))
208 port = int(myui.config("web", "port", 8000))
208 use_ipv6 = myui.configbool("web", "ipv6")
209 use_ipv6 = myui.configbool("web", "ipv6")
209 webdir_conf = myui.config("web", "webdir_conf")
210 webdir_conf = myui.config("web", "webdir_conf")
210 ssl_cert = myui.config("web", "certificate")
211 ssl_cert = myui.config("web", "certificate")
211 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
212 accesslog = openlog(myui.config("web", "accesslog", "-"), sys.stdout)
212 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
213 errorlog = openlog(myui.config("web", "errorlog", "-"), sys.stderr)
213
214
214 if use_threads:
215 if use_threads:
215 try:
216 try:
216 from threading import activeCount
217 from threading import activeCount
217 except ImportError:
218 except ImportError:
218 use_threads = False
219 use_threads = False
219
220
220 if use_threads:
221 if use_threads:
221 _mixin = SocketServer.ThreadingMixIn
222 _mixin = SocketServer.ThreadingMixIn
222 else:
223 else:
223 if hasattr(os, "fork"):
224 if hasattr(os, "fork"):
224 _mixin = SocketServer.ForkingMixIn
225 _mixin = SocketServer.ForkingMixIn
225 else:
226 else:
226 class _mixin:
227 class _mixin:
227 pass
228 pass
228
229
229 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
230 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
230
231
231 # SO_REUSEADDR has broken semantics on windows
232 # SO_REUSEADDR has broken semantics on windows
232 if os.name == 'nt':
233 if os.name == 'nt':
233 allow_reuse_address = 0
234 allow_reuse_address = 0
234
235
235 def __init__(self, *args, **kargs):
236 def __init__(self, *args, **kargs):
236 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
237 BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs)
237 self.accesslog = accesslog
238 self.accesslog = accesslog
238 self.errorlog = errorlog
239 self.errorlog = errorlog
239 self.daemon_threads = True
240 self.daemon_threads = True
240 def make_handler():
241 def make_handler():
241 if webdir_conf:
242 if webdir_conf:
242 hgwebobj = hgwebdir(webdir_conf, ui)
243 hgwebobj = hgwebdir(webdir_conf, ui)
243 elif repo is not None:
244 elif repo is not None:
244 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
245 hgwebobj = hgweb(hg.repository(repo.ui, repo.root))
245 else:
246 else:
246 raise hg.RepoError(_("There is no Mercurial repository here"
247 raise hg.RepoError(_("There is no Mercurial repository here"
247 " (.hg not found)"))
248 " (.hg not found)"))
248 return hgwebobj
249 return hgwebobj
249 self.application = make_handler()
250 self.application = make_handler()
250
251
251 addr = address
252 addr = address
252 if addr in ('', '::'):
253 if addr in ('', '::'):
253 addr = socket.gethostname()
254 addr = socket.gethostname()
254
255
255 self.addr, self.port = addr, port
256 self.addr, self.port = addr, port
256
257
257 if ssl_cert:
258 if ssl_cert:
258 try:
259 try:
259 from OpenSSL import SSL
260 from OpenSSL import SSL
260 ctx = SSL.Context(SSL.SSLv23_METHOD)
261 ctx = SSL.Context(SSL.SSLv23_METHOD)
261 except ImportError:
262 except ImportError:
262 raise util.Abort("SSL support is unavailable")
263 raise util.Abort("SSL support is unavailable")
263 ctx.use_privatekey_file(ssl_cert)
264 ctx.use_privatekey_file(ssl_cert)
264 ctx.use_certificate_file(ssl_cert)
265 ctx.use_certificate_file(ssl_cert)
265 sock = socket.socket(self.address_family, self.socket_type)
266 sock = socket.socket(self.address_family, self.socket_type)
266 self.socket = SSL.Connection(ctx, sock)
267 self.socket = SSL.Connection(ctx, sock)
267 self.server_bind()
268 self.server_bind()
268 self.server_activate()
269 self.server_activate()
269
270
270 class IPv6HTTPServer(MercurialHTTPServer):
271 class IPv6HTTPServer(MercurialHTTPServer):
271 address_family = getattr(socket, 'AF_INET6', None)
272 address_family = getattr(socket, 'AF_INET6', None)
272
273
273 def __init__(self, *args, **kwargs):
274 def __init__(self, *args, **kwargs):
274 if self.address_family is None:
275 if self.address_family is None:
275 raise hg.RepoError(_('IPv6 not available on this system'))
276 raise hg.RepoError(_('IPv6 not available on this system'))
276 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
277 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
277
278
278 if ssl_cert:
279 if ssl_cert:
279 handler = _shgwebhandler
280 handler = _shgwebhandler
280 else:
281 else:
281 handler = _hgwebhandler
282 handler = _hgwebhandler
282
283
283 try:
284 try:
284 if use_ipv6:
285 if use_ipv6:
285 return IPv6HTTPServer((address, port), handler)
286 return IPv6HTTPServer((address, port), handler)
286 else:
287 else:
287 return MercurialHTTPServer((address, port), handler)
288 return MercurialHTTPServer((address, port), handler)
288 except socket.error, inst:
289 except socket.error, inst:
289 raise util.Abort(_('cannot start server: %s') % inst.args[1])
290 raise util.Abort(_('cannot start server: %s') % inst.args[1])
@@ -1,64 +1,67 b''
1 # hgweb/wsgicgi.py - CGI->WSGI translator
1 # hgweb/wsgicgi.py - CGI->WSGI translator
2 #
2 #
3 # Copyright 2006 Eric Hopper <hopper@omnifarious.org>
3 # Copyright 2006 Eric Hopper <hopper@omnifarious.org>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # This was originally copied from the public domain code at
8 # This was originally copied from the public domain code at
9 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
9 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
10
10
11 import os, sys
11 import os, sys
12 from mercurial import util
12 from mercurial import util
13
13
14 def launch(application):
14 def launch(application):
15 util.set_binary(sys.stdin)
15 util.set_binary(sys.stdin)
16 util.set_binary(sys.stdout)
16 util.set_binary(sys.stdout)
17
17
18 environ = dict(os.environ.items())
18 environ = dict(os.environ.items())
19 if 'PATH_INFO' not in environ:
20 environ['PATH_INFO'] = ''
21
19 environ['wsgi.input'] = sys.stdin
22 environ['wsgi.input'] = sys.stdin
20 environ['wsgi.errors'] = sys.stderr
23 environ['wsgi.errors'] = sys.stderr
21 environ['wsgi.version'] = (1, 0)
24 environ['wsgi.version'] = (1, 0)
22 environ['wsgi.multithread'] = False
25 environ['wsgi.multithread'] = False
23 environ['wsgi.multiprocess'] = True
26 environ['wsgi.multiprocess'] = True
24 environ['wsgi.run_once'] = True
27 environ['wsgi.run_once'] = True
25
28
26 if environ.get('HTTPS','off').lower() in ('on','1','yes'):
29 if environ.get('HTTPS','off').lower() in ('on','1','yes'):
27 environ['wsgi.url_scheme'] = 'https'
30 environ['wsgi.url_scheme'] = 'https'
28 else:
31 else:
29 environ['wsgi.url_scheme'] = 'http'
32 environ['wsgi.url_scheme'] = 'http'
30
33
31 headers_set = []
34 headers_set = []
32 headers_sent = []
35 headers_sent = []
33 out = sys.stdout
36 out = sys.stdout
34
37
35 def write(data):
38 def write(data):
36 if not headers_set:
39 if not headers_set:
37 raise AssertionError("write() before start_response()")
40 raise AssertionError("write() before start_response()")
38
41
39 elif not headers_sent:
42 elif not headers_sent:
40 # Before the first output, send the stored headers
43 # Before the first output, send the stored headers
41 status, response_headers = headers_sent[:] = headers_set
44 status, response_headers = headers_sent[:] = headers_set
42 out.write('Status: %s\r\n' % status)
45 out.write('Status: %s\r\n' % status)
43 for header in response_headers:
46 for header in response_headers:
44 out.write('%s: %s\r\n' % header)
47 out.write('%s: %s\r\n' % header)
45 out.write('\r\n')
48 out.write('\r\n')
46
49
47 out.write(data)
50 out.write(data)
48 out.flush()
51 out.flush()
49
52
50 def start_response(status, response_headers, exc_info=None):
53 def start_response(status, response_headers, exc_info=None):
51 if exc_info:
54 if exc_info:
52 try:
55 try:
53 if headers_sent:
56 if headers_sent:
54 # Re-raise original exception if headers sent
57 # Re-raise original exception if headers sent
55 raise exc_info[0], exc_info[1], exc_info[2]
58 raise exc_info[0], exc_info[1], exc_info[2]
56 finally:
59 finally:
57 exc_info = None # avoid dangling circular ref
60 exc_info = None # avoid dangling circular ref
58 elif headers_set:
61 elif headers_set:
59 raise AssertionError("Headers already set!")
62 raise AssertionError("Headers already set!")
60
63
61 headers_set[:] = [status, response_headers]
64 headers_set[:] = [status, response_headers]
62 return write
65 return write
63
66
64 application(environ, start_response)
67 application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now