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