##// END OF EJS Templates
use untrusted settings in hgweb...
Alexis S. L. Carvalho -
r3555:88106400 default
parent child Browse files
Show More
@@ -1,1108 +1,1127
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, 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
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 14 demandload(globals(), 'urllib')
15 15 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
16 16 demandload(globals(), "mercurial:revlog,templater")
17 17 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map")
18 18 from mercurial.node import *
19 19 from mercurial.i18n import gettext as _
20 20
21 21 def _up(p):
22 22 if p[0] != "/":
23 23 p = "/" + p
24 24 if p[-1] == "/":
25 25 p = p[:-1]
26 26 up = os.path.dirname(p)
27 27 if up == "/":
28 28 return "/"
29 29 return up + "/"
30 30
31 31 def revnavgen(pos, pagelen, limit, nodefunc):
32 32 def seq(factor, limit=None):
33 33 if limit:
34 34 yield limit
35 35 if limit >= 20 and limit <= 40:
36 36 yield 50
37 37 else:
38 38 yield 1 * factor
39 39 yield 3 * factor
40 40 for f in seq(factor * 10):
41 41 yield f
42 42
43 43 def nav(**map):
44 44 l = []
45 45 last = 0
46 46 for f in seq(1, pagelen):
47 47 if f < pagelen or f <= last:
48 48 continue
49 49 if f > limit:
50 50 break
51 51 last = f
52 52 if pos + f < limit:
53 53 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
54 54 if pos - f >= 0:
55 55 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
56 56
57 57 try:
58 58 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
59 59
60 60 for label, node in l:
61 61 yield {"label": label, "node": node}
62 62
63 63 yield {"label": "tip", "node": "tip"}
64 64 except hg.RepoError:
65 65 pass
66 66
67 67 return nav
68 68
69 69 class hgweb(object):
70 70 def __init__(self, repo, name=None):
71 71 if type(repo) == type(""):
72 72 self.repo = hg.repository(ui.ui(), repo)
73 73 else:
74 74 self.repo = repo
75 75
76 76 self.mtime = -1
77 77 self.reponame = name
78 78 self.archives = 'zip', 'gz', 'bz2'
79 79 self.stripecount = 1
80 self.templatepath = self.repo.ui.config("web", "templates",
81 templater.templatepath())
80 # a repo owner may set web.templates in .hg/hgrc to get any file
81 # readable by the user running the CGI script
82 self.templatepath = self.config("web", "templates",
83 templater.templatepath(),
84 untrusted=False)
85
86 # The CGI scripts are often run by a user different from the repo owner.
87 # Trust the settings from the .hg/hgrc files by default.
88 def config(self, section, name, default=None, untrusted=True):
89 return self.repo.ui.config(section, name, default,
90 untrusted=untrusted)
91
92 def configbool(self, section, name, default=False, untrusted=True):
93 return self.repo.ui.configbool(section, name, default,
94 untrusted=untrusted)
95
96 def configlist(self, section, name, default=None, untrusted=True):
97 return self.repo.ui.configlist(section, name, default,
98 untrusted=untrusted)
82 99
83 100 def refresh(self):
84 101 mtime = get_mtime(self.repo.root)
85 102 if mtime != self.mtime:
86 103 self.mtime = mtime
87 104 self.repo = hg.repository(self.repo.ui, self.repo.root)
88 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
89 self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
90 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
91 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
92 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
105 self.maxchanges = int(self.config("web", "maxchanges", 10))
106 self.stripecount = int(self.config("web", "stripes", 1))
107 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
108 self.maxfiles = int(self.config("web", "maxfiles", 10))
109 self.allowpull = self.configbool("web", "allowpull", True)
93 110
94 111 def archivelist(self, nodeid):
95 allowed = self.repo.ui.configlist("web", "allow_archive")
112 allowed = self.configlist("web", "allow_archive")
96 113 for i, spec in self.archive_specs.iteritems():
97 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
114 if i in allowed or self.configbool("web", "allow" + i):
98 115 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
99 116
100 117 def listfilediffs(self, files, changeset):
101 118 for f in files[:self.maxfiles]:
102 119 yield self.t("filedifflink", node=hex(changeset), file=f)
103 120 if len(files) > self.maxfiles:
104 121 yield self.t("fileellipses")
105 122
106 123 def siblings(self, siblings=[], hiderev=None, **args):
107 124 siblings = [s for s in siblings if s.node() != nullid]
108 125 if len(siblings) == 1 and siblings[0].rev() == hiderev:
109 126 return
110 127 for s in siblings:
111 128 d = {'node': hex(s.node()), 'rev': s.rev()}
112 129 if hasattr(s, 'path'):
113 130 d['file'] = s.path()
114 131 d.update(args)
115 132 yield d
116 133
117 134 def renamelink(self, fl, node):
118 135 r = fl.renamed(node)
119 136 if r:
120 137 return [dict(file=r[0], node=hex(r[1]))]
121 138 return []
122 139
123 140 def showtag(self, t1, node=nullid, **args):
124 141 for t in self.repo.nodetags(node):
125 142 yield self.t(t1, tag=t, **args)
126 143
127 144 def diff(self, node1, node2, files):
128 145 def filterfiles(filters, files):
129 146 l = [x for x in files if x in filters]
130 147
131 148 for t in filters:
132 149 if t and t[-1] != os.sep:
133 150 t += os.sep
134 151 l += [x for x in files if x.startswith(t)]
135 152 return l
136 153
137 154 parity = [0]
138 155 def diffblock(diff, f, fn):
139 156 yield self.t("diffblock",
140 157 lines=prettyprintlines(diff),
141 158 parity=parity[0],
142 159 file=f,
143 160 filenode=hex(fn or nullid))
144 161 parity[0] = 1 - parity[0]
145 162
146 163 def prettyprintlines(diff):
147 164 for l in diff.splitlines(1):
148 165 if l.startswith('+'):
149 166 yield self.t("difflineplus", line=l)
150 167 elif l.startswith('-'):
151 168 yield self.t("difflineminus", line=l)
152 169 elif l.startswith('@'):
153 170 yield self.t("difflineat", line=l)
154 171 else:
155 172 yield self.t("diffline", line=l)
156 173
157 174 r = self.repo
158 175 cl = r.changelog
159 176 mf = r.manifest
160 177 change1 = cl.read(node1)
161 178 change2 = cl.read(node2)
162 179 mmap1 = mf.read(change1[0])
163 180 mmap2 = mf.read(change2[0])
164 181 date1 = util.datestr(change1[2])
165 182 date2 = util.datestr(change2[2])
166 183
167 184 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
168 185 if files:
169 186 modified, added, removed = map(lambda x: filterfiles(files, x),
170 187 (modified, added, removed))
171 188
172 diffopts = patch.diffopts(self.repo.ui)
189 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
173 190 for f in modified:
174 191 to = r.file(f).read(mmap1[f])
175 192 tn = r.file(f).read(mmap2[f])
176 193 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
177 194 opts=diffopts), f, tn)
178 195 for f in added:
179 196 to = None
180 197 tn = r.file(f).read(mmap2[f])
181 198 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
182 199 opts=diffopts), f, tn)
183 200 for f in removed:
184 201 to = r.file(f).read(mmap1[f])
185 202 tn = None
186 203 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
187 204 opts=diffopts), f, tn)
188 205
189 206 def changelog(self, ctx, shortlog=False):
190 207 def changelist(**map):
191 208 parity = (start - end) & 1
192 209 cl = self.repo.changelog
193 210 l = [] # build a list in forward order for efficiency
194 211 for i in xrange(start, end):
195 212 ctx = self.repo.changectx(i)
196 213 n = ctx.node()
197 214
198 215 l.insert(0, {"parity": parity,
199 216 "author": ctx.user(),
200 217 "parent": self.siblings(ctx.parents(), i - 1),
201 218 "child": self.siblings(ctx.children(), i + 1),
202 219 "changelogtag": self.showtag("changelogtag",n),
203 220 "desc": ctx.description(),
204 221 "date": ctx.date(),
205 222 "files": self.listfilediffs(ctx.files(), n),
206 223 "rev": i,
207 224 "node": hex(n)})
208 225 parity = 1 - parity
209 226
210 227 for e in l:
211 228 yield e
212 229
213 230 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
214 231 cl = self.repo.changelog
215 232 count = cl.count()
216 233 pos = ctx.rev()
217 234 start = max(0, pos - maxchanges + 1)
218 235 end = min(count, start + maxchanges)
219 236 pos = end - 1
220 237
221 238 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
222 239
223 240 yield self.t(shortlog and 'shortlog' or 'changelog',
224 241 changenav=changenav,
225 242 node=hex(cl.tip()),
226 243 rev=pos, changesets=count, entries=changelist,
227 244 archives=self.archivelist("tip"))
228 245
229 246 def search(self, query):
230 247
231 248 def changelist(**map):
232 249 cl = self.repo.changelog
233 250 count = 0
234 251 qw = query.lower().split()
235 252
236 253 def revgen():
237 254 for i in xrange(cl.count() - 1, 0, -100):
238 255 l = []
239 256 for j in xrange(max(0, i - 100), i):
240 257 ctx = self.repo.changectx(j)
241 258 l.append(ctx)
242 259 l.reverse()
243 260 for e in l:
244 261 yield e
245 262
246 263 for ctx in revgen():
247 264 miss = 0
248 265 for q in qw:
249 266 if not (q in ctx.user().lower() or
250 267 q in ctx.description().lower() or
251 268 q in " ".join(ctx.files()[:20]).lower()):
252 269 miss = 1
253 270 break
254 271 if miss:
255 272 continue
256 273
257 274 count += 1
258 275 n = ctx.node()
259 276
260 277 yield self.t('searchentry',
261 278 parity=self.stripes(count),
262 279 author=ctx.user(),
263 280 parent=self.siblings(ctx.parents()),
264 281 child=self.siblings(ctx.children()),
265 282 changelogtag=self.showtag("changelogtag",n),
266 283 desc=ctx.description(),
267 284 date=ctx.date(),
268 285 files=self.listfilediffs(ctx.files(), n),
269 286 rev=ctx.rev(),
270 287 node=hex(n))
271 288
272 289 if count >= self.maxchanges:
273 290 break
274 291
275 292 cl = self.repo.changelog
276 293
277 294 yield self.t('search',
278 295 query=query,
279 296 node=hex(cl.tip()),
280 297 entries=changelist)
281 298
282 299 def changeset(self, ctx):
283 300 n = ctx.node()
284 301 parents = ctx.parents()
285 302 p1 = parents[0].node()
286 303
287 304 files = []
288 305 parity = 0
289 306 for f in ctx.files():
290 307 files.append(self.t("filenodelink",
291 308 node=hex(n), file=f,
292 309 parity=parity))
293 310 parity = 1 - parity
294 311
295 312 def diff(**map):
296 313 yield self.diff(p1, n, None)
297 314
298 315 yield self.t('changeset',
299 316 diff=diff,
300 317 rev=ctx.rev(),
301 318 node=hex(n),
302 319 parent=self.siblings(parents),
303 320 child=self.siblings(ctx.children()),
304 321 changesettag=self.showtag("changesettag",n),
305 322 author=ctx.user(),
306 323 desc=ctx.description(),
307 324 date=ctx.date(),
308 325 files=files,
309 326 archives=self.archivelist(hex(n)))
310 327
311 328 def filelog(self, fctx):
312 329 f = fctx.path()
313 330 fl = fctx.filelog()
314 331 count = fl.count()
315 332 pagelen = self.maxshortchanges
316 333 pos = fctx.filerev()
317 334 start = max(0, pos - pagelen + 1)
318 335 end = min(count, start + pagelen)
319 336 pos = end - 1
320 337
321 338 def entries(**map):
322 339 l = []
323 340 parity = (count - 1) & 1
324 341
325 342 for i in xrange(start, end):
326 343 ctx = fctx.filectx(i)
327 344 n = fl.node(i)
328 345
329 346 l.insert(0, {"parity": parity,
330 347 "filerev": i,
331 348 "file": f,
332 349 "node": hex(ctx.node()),
333 350 "author": ctx.user(),
334 351 "date": ctx.date(),
335 352 "rename": self.renamelink(fl, n),
336 353 "parent": self.siblings(fctx.parents()),
337 354 "child": self.siblings(fctx.children()),
338 355 "desc": ctx.description()})
339 356 parity = 1 - parity
340 357
341 358 for e in l:
342 359 yield e
343 360
344 361 nodefunc = lambda x: fctx.filectx(fileid=x)
345 362 nav = revnavgen(pos, pagelen, count, nodefunc)
346 363 yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
347 364 entries=entries)
348 365
349 366 def filerevision(self, fctx):
350 367 f = fctx.path()
351 368 text = fctx.data()
352 369 fl = fctx.filelog()
353 370 n = fctx.filenode()
354 371
355 372 mt = mimetypes.guess_type(f)[0]
356 373 rawtext = text
357 374 if util.binary(text):
358 375 mt = mt or 'application/octet-stream'
359 376 text = "(binary:%s)" % mt
360 377 mt = mt or 'text/plain'
361 378
362 379 def lines():
363 380 for l, t in enumerate(text.splitlines(1)):
364 381 yield {"line": t,
365 382 "linenumber": "% 6d" % (l + 1),
366 383 "parity": self.stripes(l)}
367 384
368 385 yield self.t("filerevision",
369 386 file=f,
370 387 path=_up(f),
371 388 text=lines(),
372 389 raw=rawtext,
373 390 mimetype=mt,
374 391 rev=fctx.rev(),
375 392 node=hex(fctx.node()),
376 393 author=fctx.user(),
377 394 date=fctx.date(),
378 395 desc=fctx.description(),
379 396 parent=self.siblings(fctx.parents()),
380 397 child=self.siblings(fctx.children()),
381 398 rename=self.renamelink(fl, n),
382 399 permissions=fctx.manifest().execf(f))
383 400
384 401 def fileannotate(self, fctx):
385 402 f = fctx.path()
386 403 n = fctx.filenode()
387 404 fl = fctx.filelog()
388 405
389 406 def annotate(**map):
390 407 parity = 0
391 408 last = None
392 409 for f, l in fctx.annotate(follow=True):
393 410 fnode = f.filenode()
394 411 name = self.repo.ui.shortuser(f.user())
395 412
396 413 if last != fnode:
397 414 parity = 1 - parity
398 415 last = fnode
399 416
400 417 yield {"parity": parity,
401 418 "node": hex(f.node()),
402 419 "rev": f.rev(),
403 420 "author": name,
404 421 "file": f.path(),
405 422 "line": l}
406 423
407 424 yield self.t("fileannotate",
408 425 file=f,
409 426 annotate=annotate,
410 427 path=_up(f),
411 428 rev=fctx.rev(),
412 429 node=hex(fctx.node()),
413 430 author=fctx.user(),
414 431 date=fctx.date(),
415 432 desc=fctx.description(),
416 433 rename=self.renamelink(fl, n),
417 434 parent=self.siblings(fctx.parents()),
418 435 child=self.siblings(fctx.children()),
419 436 permissions=fctx.manifest().execf(f))
420 437
421 438 def manifest(self, ctx, path):
422 439 mf = ctx.manifest()
423 440 node = ctx.node()
424 441
425 442 files = {}
426 443
427 444 p = path[1:]
428 445 if p and p[-1] != "/":
429 446 p += "/"
430 447 l = len(p)
431 448
432 449 for f,n in mf.items():
433 450 if f[:l] != p:
434 451 continue
435 452 remain = f[l:]
436 453 if "/" in remain:
437 454 short = remain[:remain.index("/") + 1] # bleah
438 455 files[short] = (f, None)
439 456 else:
440 457 short = os.path.basename(remain)
441 458 files[short] = (f, n)
442 459
443 460 def filelist(**map):
444 461 parity = 0
445 462 fl = files.keys()
446 463 fl.sort()
447 464 for f in fl:
448 465 full, fnode = files[f]
449 466 if not fnode:
450 467 continue
451 468
452 469 yield {"file": full,
453 470 "parity": self.stripes(parity),
454 471 "basename": f,
455 472 "size": ctx.filectx(full).size(),
456 473 "permissions": mf.execf(full)}
457 474 parity += 1
458 475
459 476 def dirlist(**map):
460 477 parity = 0
461 478 fl = files.keys()
462 479 fl.sort()
463 480 for f in fl:
464 481 full, fnode = files[f]
465 482 if fnode:
466 483 continue
467 484
468 485 yield {"parity": self.stripes(parity),
469 486 "path": os.path.join(path, f),
470 487 "basename": f[:-1]}
471 488 parity += 1
472 489
473 490 yield self.t("manifest",
474 491 rev=ctx.rev(),
475 492 node=hex(node),
476 493 path=path,
477 494 up=_up(path),
478 495 fentries=filelist,
479 496 dentries=dirlist,
480 497 archives=self.archivelist(hex(node)))
481 498
482 499 def tags(self):
483 500 cl = self.repo.changelog
484 501
485 502 i = self.repo.tagslist()
486 503 i.reverse()
487 504
488 505 def entries(notip=False, **map):
489 506 parity = 0
490 507 for k,n in i:
491 508 if notip and k == "tip": continue
492 509 yield {"parity": self.stripes(parity),
493 510 "tag": k,
494 511 "date": cl.read(n)[2],
495 512 "node": hex(n)}
496 513 parity += 1
497 514
498 515 yield self.t("tags",
499 516 node=hex(self.repo.changelog.tip()),
500 517 entries=lambda **x: entries(False, **x),
501 518 entriesnotip=lambda **x: entries(True, **x))
502 519
503 520 def summary(self):
504 521 cl = self.repo.changelog
505 522
506 523 i = self.repo.tagslist()
507 524 i.reverse()
508 525
509 526 def tagentries(**map):
510 527 parity = 0
511 528 count = 0
512 529 for k,n in i:
513 530 if k == "tip": # skip tip
514 531 continue;
515 532
516 533 count += 1
517 534 if count > 10: # limit to 10 tags
518 535 break;
519 536
520 537 c = cl.read(n)
521 538 t = c[2]
522 539
523 540 yield self.t("tagentry",
524 541 parity = self.stripes(parity),
525 542 tag = k,
526 543 node = hex(n),
527 544 date = t)
528 545 parity += 1
529 546
530 547 def heads(**map):
531 548 parity = 0
532 549 count = 0
533 550
534 551 for node in self.repo.heads():
535 552 count += 1
536 553 if count > 10:
537 554 break;
538 555
539 556 ctx = self.repo.changectx(node)
540 557
541 558 yield {'parity': self.stripes(parity),
542 559 'branch': ctx.branch(),
543 560 'node': hex(node),
544 561 'date': ctx.date()}
545 562 parity += 1
546 563
547 564 def changelist(**map):
548 565 parity = 0
549 566 cl = self.repo.changelog
550 567 l = [] # build a list in forward order for efficiency
551 568 for i in xrange(start, end):
552 569 n = cl.node(i)
553 570 changes = cl.read(n)
554 571 hn = hex(n)
555 572 t = changes[2]
556 573
557 574 l.insert(0, self.t(
558 575 'shortlogentry',
559 576 parity = parity,
560 577 author = changes[1],
561 578 desc = changes[4],
562 579 date = t,
563 580 rev = i,
564 581 node = hn))
565 582 parity = 1 - parity
566 583
567 584 yield l
568 585
569 586 count = cl.count()
570 587 start = max(0, count - self.maxchanges)
571 588 end = min(count, start + self.maxchanges)
572 589
573 590 yield self.t("summary",
574 desc = self.repo.ui.config("web", "description", "unknown"),
575 owner = (self.repo.ui.config("ui", "username") or # preferred
576 self.repo.ui.config("web", "contact") or # deprecated
577 self.repo.ui.config("web", "author", "unknown")), # also
591 desc = self.config("web", "description", "unknown"),
592 owner = (self.config("ui", "username") or # preferred
593 self.config("web", "contact") or # deprecated
594 self.config("web", "author", "unknown")), # also
578 595 lastchange = cl.read(cl.tip())[2],
579 596 tags = tagentries,
580 597 heads = heads,
581 598 shortlog = changelist,
582 599 node = hex(cl.tip()),
583 600 archives=self.archivelist("tip"))
584 601
585 602 def filediff(self, fctx):
586 603 n = fctx.node()
587 604 path = fctx.path()
588 605 parents = fctx.parents()
589 606 p1 = parents and parents[0].node() or nullid
590 607
591 608 def diff(**map):
592 609 yield self.diff(p1, n, [path])
593 610
594 611 yield self.t("filediff",
595 612 file=path,
596 613 node=hex(n),
597 614 rev=fctx.rev(),
598 615 parent=self.siblings(parents),
599 616 child=self.siblings(fctx.children()),
600 617 diff=diff)
601 618
602 619 archive_specs = {
603 620 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
604 621 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
605 622 'zip': ('application/zip', 'zip', '.zip', None),
606 623 }
607 624
608 625 def archive(self, req, cnode, type_):
609 626 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
610 627 name = "%s-%s" % (reponame, short(cnode))
611 628 mimetype, artype, extension, encoding = self.archive_specs[type_]
612 629 headers = [('Content-type', mimetype),
613 630 ('Content-disposition', 'attachment; filename=%s%s' %
614 631 (name, extension))]
615 632 if encoding:
616 633 headers.append(('Content-encoding', encoding))
617 634 req.header(headers)
618 635 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
619 636
620 637 # add tags to things
621 638 # tags -> list of changesets corresponding to tags
622 639 # find tag, changeset, file
623 640
624 641 def cleanpath(self, path):
625 642 return util.canonpath(self.repo.root, '', path)
626 643
627 644 def run(self):
628 645 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
629 646 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
630 647 import mercurial.hgweb.wsgicgi as wsgicgi
631 648 from request import wsgiapplication
632 649 def make_web_app():
633 650 return self
634 651 wsgicgi.launch(wsgiapplication(make_web_app))
635 652
636 653 def run_wsgi(self, req):
637 654 def header(**map):
638 655 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
639 656 msg = mimetools.Message(header_file, 0)
640 657 req.header(msg.items())
641 658 yield header_file.read()
642 659
643 660 def rawfileheader(**map):
644 661 req.header([('Content-type', map['mimetype']),
645 662 ('Content-disposition', 'filename=%s' % map['file']),
646 663 ('Content-length', str(len(map['raw'])))])
647 664 yield ''
648 665
649 666 def footer(**map):
650 667 yield self.t("footer", **map)
651 668
652 669 def motd(**map):
653 yield self.repo.ui.config("web", "motd", "")
670 yield self.config("web", "motd", "")
654 671
655 672 def expand_form(form):
656 673 shortcuts = {
657 674 'cl': [('cmd', ['changelog']), ('rev', None)],
658 675 'sl': [('cmd', ['shortlog']), ('rev', None)],
659 676 'cs': [('cmd', ['changeset']), ('node', None)],
660 677 'f': [('cmd', ['file']), ('filenode', None)],
661 678 'fl': [('cmd', ['filelog']), ('filenode', None)],
662 679 'fd': [('cmd', ['filediff']), ('node', None)],
663 680 'fa': [('cmd', ['annotate']), ('filenode', None)],
664 681 'mf': [('cmd', ['manifest']), ('manifest', None)],
665 682 'ca': [('cmd', ['archive']), ('node', None)],
666 683 'tags': [('cmd', ['tags'])],
667 684 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
668 685 'static': [('cmd', ['static']), ('file', None)]
669 686 }
670 687
671 688 for k in shortcuts.iterkeys():
672 689 if form.has_key(k):
673 690 for name, value in shortcuts[k]:
674 691 if value is None:
675 692 value = form[k]
676 693 form[name] = value
677 694 del form[k]
678 695
679 696 def rewrite_request(req):
680 697 '''translate new web interface to traditional format'''
681 698
682 699 def spliturl(req):
683 700 def firstitem(query):
684 701 return query.split('&', 1)[0].split(';', 1)[0]
685 702
686 703 def normurl(url):
687 704 inner = '/'.join([x for x in url.split('/') if x])
688 705 tl = len(url) > 1 and url.endswith('/') and '/' or ''
689 706
690 707 return '%s%s%s' % (url.startswith('/') and '/' or '',
691 708 inner, tl)
692 709
693 710 root = normurl(req.env.get('REQUEST_URI', '').split('?', 1)[0])
694 711 pi = normurl(req.env.get('PATH_INFO', ''))
695 712 if pi:
696 713 # strip leading /
697 714 pi = pi[1:]
698 715 if pi:
699 716 root = root[:-len(pi)]
700 717 if req.env.has_key('REPO_NAME'):
701 718 rn = req.env['REPO_NAME'] + '/'
702 719 root += rn
703 720 query = pi[len(rn):]
704 721 else:
705 722 query = pi
706 723 else:
707 724 root += '?'
708 725 query = firstitem(req.env['QUERY_STRING'])
709 726
710 727 return (root, query)
711 728
712 729 req.url, query = spliturl(req)
713 730
714 731 if req.form.has_key('cmd'):
715 732 # old style
716 733 return
717 734
718 735 args = query.split('/', 2)
719 736 if not args or not args[0]:
720 737 return
721 738
722 739 cmd = args.pop(0)
723 740 style = cmd.rfind('-')
724 741 if style != -1:
725 742 req.form['style'] = [cmd[:style]]
726 743 cmd = cmd[style+1:]
727 744 # avoid accepting e.g. style parameter as command
728 745 if hasattr(self, 'do_' + cmd):
729 746 req.form['cmd'] = [cmd]
730 747
731 748 if args and args[0]:
732 749 node = args.pop(0)
733 750 req.form['node'] = [node]
734 751 if args:
735 752 req.form['file'] = args
736 753
737 754 if cmd == 'static':
738 755 req.form['file'] = req.form['node']
739 756 elif cmd == 'archive':
740 757 fn = req.form['node'][0]
741 758 for type_, spec in self.archive_specs.iteritems():
742 759 ext = spec[2]
743 760 if fn.endswith(ext):
744 761 req.form['node'] = [fn[:-len(ext)]]
745 762 req.form['type'] = [type_]
746 763
747 764 def sessionvars(**map):
748 765 fields = []
749 766 if req.form.has_key('style'):
750 767 style = req.form['style'][0]
751 if style != self.repo.ui.config('web', 'style', ''):
768 if style != self.config('web', 'style', ''):
752 769 fields.append(('style', style))
753 770
754 771 separator = req.url[-1] == '?' and ';' or '?'
755 772 for name, value in fields:
756 773 yield dict(name=name, value=value, separator=separator)
757 774 separator = ';'
758 775
759 776 self.refresh()
760 777
761 778 expand_form(req.form)
762 779 rewrite_request(req)
763 780
764 style = self.repo.ui.config("web", "style", "")
781 style = self.config("web", "style", "")
765 782 if req.form.has_key('style'):
766 783 style = req.form['style'][0]
767 784 mapfile = style_map(self.templatepath, style)
768 785
769 786 port = req.env["SERVER_PORT"]
770 787 port = port != "80" and (":" + port) or ""
771 788 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
772 789
773 790 if not self.reponame:
774 self.reponame = (self.repo.ui.config("web", "name")
791 self.reponame = (self.config("web", "name")
775 792 or req.env.get('REPO_NAME')
776 793 or req.url.strip('/') or self.repo.root)
777 794
778 795 self.t = templater.templater(mapfile, templater.common_filters,
779 796 defaults={"url": req.url,
780 797 "urlbase": urlbase,
781 798 "repo": self.reponame,
782 799 "header": header,
783 800 "footer": footer,
784 801 "motd": motd,
785 802 "rawfileheader": rawfileheader,
786 803 "sessionvars": sessionvars
787 804 })
788 805
789 806 if not req.form.has_key('cmd'):
790 807 req.form['cmd'] = [self.t.cache['default'],]
791 808
792 809 cmd = req.form['cmd'][0]
793 810
794 811 method = getattr(self, 'do_' + cmd, None)
795 812 if method:
796 813 try:
797 814 method(req)
798 815 except (hg.RepoError, revlog.RevlogError), inst:
799 816 req.write(self.t("error", error=str(inst)))
800 817 else:
801 818 req.write(self.t("error", error='No such method: ' + cmd))
802 819
803 820 def changectx(self, req):
804 821 if req.form.has_key('node'):
805 822 changeid = req.form['node'][0]
806 823 elif req.form.has_key('manifest'):
807 824 changeid = req.form['manifest'][0]
808 825 else:
809 826 changeid = self.repo.changelog.count() - 1
810 827
811 828 try:
812 829 ctx = self.repo.changectx(changeid)
813 830 except hg.RepoError:
814 831 man = self.repo.manifest
815 832 mn = man.lookup(changeid)
816 833 ctx = self.repo.changectx(man.linkrev(mn))
817 834
818 835 return ctx
819 836
820 837 def filectx(self, req):
821 838 path = self.cleanpath(req.form['file'][0])
822 839 if req.form.has_key('node'):
823 840 changeid = req.form['node'][0]
824 841 else:
825 842 changeid = req.form['filenode'][0]
826 843 try:
827 844 ctx = self.repo.changectx(changeid)
828 845 fctx = ctx.filectx(path)
829 846 except hg.RepoError:
830 847 fctx = self.repo.filectx(path, fileid=changeid)
831 848
832 849 return fctx
833 850
834 851 def stripes(self, parity):
835 852 "make horizontal stripes for easier reading"
836 853 if self.stripecount:
837 854 return (1 + parity / self.stripecount) & 1
838 855 else:
839 856 return 0
840 857
841 858 def do_log(self, req):
842 859 if req.form.has_key('file') and req.form['file'][0]:
843 860 self.do_filelog(req)
844 861 else:
845 862 self.do_changelog(req)
846 863
847 864 def do_rev(self, req):
848 865 self.do_changeset(req)
849 866
850 867 def do_file(self, req):
851 868 path = req.form.get('file', [''])[0]
852 869 if path:
853 870 try:
854 871 req.write(self.filerevision(self.filectx(req)))
855 872 return
856 873 except hg.RepoError:
857 874 pass
858 875 path = self.cleanpath(path)
859 876
860 877 req.write(self.manifest(self.changectx(req), '/' + path))
861 878
862 879 def do_diff(self, req):
863 880 self.do_filediff(req)
864 881
865 882 def do_changelog(self, req, shortlog = False):
866 883 if req.form.has_key('node'):
867 884 ctx = self.changectx(req)
868 885 else:
869 886 if req.form.has_key('rev'):
870 887 hi = req.form['rev'][0]
871 888 else:
872 889 hi = self.repo.changelog.count() - 1
873 890 try:
874 891 ctx = self.repo.changectx(hi)
875 892 except hg.RepoError:
876 893 req.write(self.search(hi)) # XXX redirect to 404 page?
877 894 return
878 895
879 896 req.write(self.changelog(ctx, shortlog = shortlog))
880 897
881 898 def do_shortlog(self, req):
882 899 self.do_changelog(req, shortlog = True)
883 900
884 901 def do_changeset(self, req):
885 902 req.write(self.changeset(self.changectx(req)))
886 903
887 904 def do_manifest(self, req):
888 905 req.write(self.manifest(self.changectx(req),
889 906 self.cleanpath(req.form['path'][0])))
890 907
891 908 def do_tags(self, req):
892 909 req.write(self.tags())
893 910
894 911 def do_summary(self, req):
895 912 req.write(self.summary())
896 913
897 914 def do_filediff(self, req):
898 915 req.write(self.filediff(self.filectx(req)))
899 916
900 917 def do_annotate(self, req):
901 918 req.write(self.fileannotate(self.filectx(req)))
902 919
903 920 def do_filelog(self, req):
904 921 req.write(self.filelog(self.filectx(req)))
905 922
906 923 def do_lookup(self, req):
907 924 try:
908 925 r = hex(self.repo.lookup(req.form['key'][0]))
909 926 success = 1
910 927 except Exception,inst:
911 928 r = str(inst)
912 929 success = 0
913 930 resp = "%s %s\n" % (success, r)
914 931 req.httphdr("application/mercurial-0.1", length=len(resp))
915 932 req.write(resp)
916 933
917 934 def do_heads(self, req):
918 935 resp = " ".join(map(hex, self.repo.heads())) + "\n"
919 936 req.httphdr("application/mercurial-0.1", length=len(resp))
920 937 req.write(resp)
921 938
922 939 def do_branches(self, req):
923 940 nodes = []
924 941 if req.form.has_key('nodes'):
925 942 nodes = map(bin, req.form['nodes'][0].split(" "))
926 943 resp = cStringIO.StringIO()
927 944 for b in self.repo.branches(nodes):
928 945 resp.write(" ".join(map(hex, b)) + "\n")
929 946 resp = resp.getvalue()
930 947 req.httphdr("application/mercurial-0.1", length=len(resp))
931 948 req.write(resp)
932 949
933 950 def do_between(self, req):
934 951 if req.form.has_key('pairs'):
935 952 pairs = [map(bin, p.split("-"))
936 953 for p in req.form['pairs'][0].split(" ")]
937 954 resp = cStringIO.StringIO()
938 955 for b in self.repo.between(pairs):
939 956 resp.write(" ".join(map(hex, b)) + "\n")
940 957 resp = resp.getvalue()
941 958 req.httphdr("application/mercurial-0.1", length=len(resp))
942 959 req.write(resp)
943 960
944 961 def do_changegroup(self, req):
945 962 req.httphdr("application/mercurial-0.1")
946 963 nodes = []
947 964 if not self.allowpull:
948 965 return
949 966
950 967 if req.form.has_key('roots'):
951 968 nodes = map(bin, req.form['roots'][0].split(" "))
952 969
953 970 z = zlib.compressobj()
954 971 f = self.repo.changegroup(nodes, 'serve')
955 972 while 1:
956 973 chunk = f.read(4096)
957 974 if not chunk:
958 975 break
959 976 req.write(z.compress(chunk))
960 977
961 978 req.write(z.flush())
962 979
963 980 def do_changegroupsubset(self, req):
964 981 req.httphdr("application/mercurial-0.1")
965 982 bases = []
966 983 heads = []
967 984 if not self.allowpull:
968 985 return
969 986
970 987 if req.form.has_key('bases'):
971 988 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
972 989 if req.form.has_key('heads'):
973 990 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
974 991
975 992 z = zlib.compressobj()
976 993 f = self.repo.changegroupsubset(bases, heads, 'serve')
977 994 while 1:
978 995 chunk = f.read(4096)
979 996 if not chunk:
980 997 break
981 998 req.write(z.compress(chunk))
982 999
983 1000 req.write(z.flush())
984 1001
985 1002 def do_archive(self, req):
986 1003 changeset = self.repo.lookup(req.form['node'][0])
987 1004 type_ = req.form['type'][0]
988 allowed = self.repo.ui.configlist("web", "allow_archive")
1005 allowed = self.configlist("web", "allow_archive")
989 1006 if (type_ in self.archives and (type_ in allowed or
990 self.repo.ui.configbool("web", "allow" + type_, False))):
1007 self.configbool("web", "allow" + type_, False))):
991 1008 self.archive(req, changeset, type_)
992 1009 return
993 1010
994 1011 req.write(self.t("error"))
995 1012
996 1013 def do_static(self, req):
997 1014 fname = req.form['file'][0]
998 static = self.repo.ui.config("web", "static",
999 os.path.join(self.templatepath,
1000 "static"))
1015 # a repo owner may set web.static in .hg/hgrc to get any file
1016 # readable by the user running the CGI script
1017 static = self.config("web", "static",
1018 os.path.join(self.templatepath, "static"),
1019 untrusted=False)
1001 1020 req.write(staticfile(static, fname, req)
1002 1021 or self.t("error", error="%r not found" % fname))
1003 1022
1004 1023 def do_capabilities(self, req):
1005 1024 caps = ['unbundle', 'lookup', 'changegroupsubset']
1006 if self.repo.ui.configbool('server', 'uncompressed'):
1025 if self.configbool('server', 'uncompressed'):
1007 1026 caps.append('stream=%d' % self.repo.revlogversion)
1008 1027 resp = ' '.join(caps)
1009 1028 req.httphdr("application/mercurial-0.1", length=len(resp))
1010 1029 req.write(resp)
1011 1030
1012 1031 def check_perm(self, req, op, default):
1013 1032 '''check permission for operation based on user auth.
1014 1033 return true if op allowed, else false.
1015 1034 default is policy to use if no config given.'''
1016 1035
1017 1036 user = req.env.get('REMOTE_USER')
1018 1037
1019 deny = self.repo.ui.configlist('web', 'deny_' + op)
1038 deny = self.configlist('web', 'deny_' + op)
1020 1039 if deny and (not user or deny == ['*'] or user in deny):
1021 1040 return False
1022 1041
1023 allow = self.repo.ui.configlist('web', 'allow_' + op)
1042 allow = self.configlist('web', 'allow_' + op)
1024 1043 return (allow and (allow == ['*'] or user in allow)) or default
1025 1044
1026 1045 def do_unbundle(self, req):
1027 1046 def bail(response, headers={}):
1028 1047 length = int(req.env['CONTENT_LENGTH'])
1029 1048 for s in util.filechunkiter(req, limit=length):
1030 1049 # drain incoming bundle, else client will not see
1031 1050 # response when run outside cgi script
1032 1051 pass
1033 1052 req.httphdr("application/mercurial-0.1", headers=headers)
1034 1053 req.write('0\n')
1035 1054 req.write(response)
1036 1055
1037 1056 # require ssl by default, auth info cannot be sniffed and
1038 1057 # replayed
1039 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
1058 ssl_req = self.configbool('web', 'push_ssl', True)
1040 1059 if ssl_req:
1041 1060 if not req.env.get('HTTPS'):
1042 1061 bail(_('ssl required\n'))
1043 1062 return
1044 1063 proto = 'https'
1045 1064 else:
1046 1065 proto = 'http'
1047 1066
1048 1067 # do not allow push unless explicitly allowed
1049 1068 if not self.check_perm(req, 'push', False):
1050 1069 bail(_('push not authorized\n'),
1051 1070 headers={'status': '401 Unauthorized'})
1052 1071 return
1053 1072
1054 1073 req.httphdr("application/mercurial-0.1")
1055 1074
1056 1075 their_heads = req.form['heads'][0].split(' ')
1057 1076
1058 1077 def check_heads():
1059 1078 heads = map(hex, self.repo.heads())
1060 1079 return their_heads == [hex('force')] or their_heads == heads
1061 1080
1062 1081 # fail early if possible
1063 1082 if not check_heads():
1064 1083 bail(_('unsynced changes\n'))
1065 1084 return
1066 1085
1067 1086 # do not lock repo until all changegroup data is
1068 1087 # streamed. save to temporary file.
1069 1088
1070 1089 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
1071 1090 fp = os.fdopen(fd, 'wb+')
1072 1091 try:
1073 1092 length = int(req.env['CONTENT_LENGTH'])
1074 1093 for s in util.filechunkiter(req, limit=length):
1075 1094 fp.write(s)
1076 1095
1077 1096 lock = self.repo.lock()
1078 1097 try:
1079 1098 if not check_heads():
1080 1099 req.write('0\n')
1081 1100 req.write(_('unsynced changes\n'))
1082 1101 return
1083 1102
1084 1103 fp.seek(0)
1085 1104
1086 1105 # send addchangegroup output to client
1087 1106
1088 1107 old_stdout = sys.stdout
1089 1108 sys.stdout = cStringIO.StringIO()
1090 1109
1091 1110 try:
1092 1111 url = 'remote:%s:%s' % (proto,
1093 1112 req.env.get('REMOTE_HOST', ''))
1094 1113 ret = self.repo.addchangegroup(fp, 'serve', url)
1095 1114 finally:
1096 1115 val = sys.stdout.getvalue()
1097 1116 sys.stdout = old_stdout
1098 1117 req.write('%d\n' % ret)
1099 1118 req.write(val)
1100 1119 finally:
1101 1120 lock.release()
1102 1121 finally:
1103 1122 fp.close()
1104 1123 os.unlink(tempname)
1105 1124
1106 1125 def do_stream_out(self, req):
1107 1126 req.httphdr("application/mercurial-0.1")
1108 1127 streamclone.stream_out(self.repo, req)
General Comments 0
You need to be logged in to leave comments. Login now