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