comparison mercurial/hgweb/hgweb_mod.py @ 3541:881064004fd0

use untrusted settings in hgweb The only exceptions are web.static and web.templates, since they can be used to get any file that is readable by the user running the CGI script. Other options can be (ab)used to increase the use of the cpu (allow_bz2) or of the bandwidth (server.uncompressed), but they're trusted anyway.
author Alexis S. L. Carvalho <alexis@cecm.usp.br>
date Thu, 26 Oct 2006 19:25:45 +0200
parents e0db0b7934f2
children f7dee427cd14
comparison
equal deleted inserted replaced
3540:da3ee7ca620f 3541:881064004fd0
75 75
76 self.mtime = -1 76 self.mtime = -1
77 self.reponame = name 77 self.reponame = name
78 self.archives = 'zip', 'gz', 'bz2' 78 self.archives = 'zip', 'gz', 'bz2'
79 self.stripecount = 1 79 self.stripecount = 1
80 self.templatepath = self.repo.ui.config("web", "templates", 80 # a repo owner may set web.templates in .hg/hgrc to get any file
81 templater.templatepath()) 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 def refresh(self): 100 def refresh(self):
84 mtime = get_mtime(self.repo.root) 101 mtime = get_mtime(self.repo.root)
85 if mtime != self.mtime: 102 if mtime != self.mtime:
86 self.mtime = mtime 103 self.mtime = mtime
87 self.repo = hg.repository(self.repo.ui, self.repo.root) 104 self.repo = hg.repository(self.repo.ui, self.repo.root)
88 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10)) 105 self.maxchanges = int(self.config("web", "maxchanges", 10))
89 self.stripecount = int(self.repo.ui.config("web", "stripes", 1)) 106 self.stripecount = int(self.config("web", "stripes", 1))
90 self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60)) 107 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
91 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10)) 108 self.maxfiles = int(self.config("web", "maxfiles", 10))
92 self.allowpull = self.repo.ui.configbool("web", "allowpull", True) 109 self.allowpull = self.configbool("web", "allowpull", True)
93 110
94 def archivelist(self, nodeid): 111 def archivelist(self, nodeid):
95 allowed = self.repo.ui.configlist("web", "allow_archive") 112 allowed = self.configlist("web", "allow_archive")
96 for i, spec in self.archive_specs.iteritems(): 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 yield {"type" : i, "extension" : spec[2], "node" : nodeid} 115 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
99 116
100 def listfilediffs(self, files, changeset): 117 def listfilediffs(self, files, changeset):
101 for f in files[:self.maxfiles]: 118 for f in files[:self.maxfiles]:
102 yield self.t("filedifflink", node=hex(changeset), file=f) 119 yield self.t("filedifflink", node=hex(changeset), file=f)
167 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5] 184 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
168 if files: 185 if files:
169 modified, added, removed = map(lambda x: filterfiles(files, x), 186 modified, added, removed = map(lambda x: filterfiles(files, x),
170 (modified, added, removed)) 187 (modified, added, removed))
171 188
172 diffopts = patch.diffopts(self.repo.ui) 189 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
173 for f in modified: 190 for f in modified:
174 to = r.file(f).read(mmap1[f]) 191 to = r.file(f).read(mmap1[f])
175 tn = r.file(f).read(mmap2[f]) 192 tn = r.file(f).read(mmap2[f])
176 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, 193 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
177 opts=diffopts), f, tn) 194 opts=diffopts), f, tn)
569 count = cl.count() 586 count = cl.count()
570 start = max(0, count - self.maxchanges) 587 start = max(0, count - self.maxchanges)
571 end = min(count, start + self.maxchanges) 588 end = min(count, start + self.maxchanges)
572 589
573 yield self.t("summary", 590 yield self.t("summary",
574 desc = self.repo.ui.config("web", "description", "unknown"), 591 desc = self.config("web", "description", "unknown"),
575 owner = (self.repo.ui.config("ui", "username") or # preferred 592 owner = (self.config("ui", "username") or # preferred
576 self.repo.ui.config("web", "contact") or # deprecated 593 self.config("web", "contact") or # deprecated
577 self.repo.ui.config("web", "author", "unknown")), # also 594 self.config("web", "author", "unknown")), # also
578 lastchange = cl.read(cl.tip())[2], 595 lastchange = cl.read(cl.tip())[2],
579 tags = tagentries, 596 tags = tagentries,
580 heads = heads, 597 heads = heads,
581 shortlog = changelist, 598 shortlog = changelist,
582 node = hex(cl.tip()), 599 node = hex(cl.tip()),
648 665
649 def footer(**map): 666 def footer(**map):
650 yield self.t("footer", **map) 667 yield self.t("footer", **map)
651 668
652 def motd(**map): 669 def motd(**map):
653 yield self.repo.ui.config("web", "motd", "") 670 yield self.config("web", "motd", "")
654 671
655 def expand_form(form): 672 def expand_form(form):
656 shortcuts = { 673 shortcuts = {
657 'cl': [('cmd', ['changelog']), ('rev', None)], 674 'cl': [('cmd', ['changelog']), ('rev', None)],
658 'sl': [('cmd', ['shortlog']), ('rev', None)], 675 'sl': [('cmd', ['shortlog']), ('rev', None)],
746 763
747 def sessionvars(**map): 764 def sessionvars(**map):
748 fields = [] 765 fields = []
749 if req.form.has_key('style'): 766 if req.form.has_key('style'):
750 style = req.form['style'][0] 767 style = req.form['style'][0]
751 if style != self.repo.ui.config('web', 'style', ''): 768 if style != self.config('web', 'style', ''):
752 fields.append(('style', style)) 769 fields.append(('style', style))
753 770
754 separator = req.url[-1] == '?' and ';' or '?' 771 separator = req.url[-1] == '?' and ';' or '?'
755 for name, value in fields: 772 for name, value in fields:
756 yield dict(name=name, value=value, separator=separator) 773 yield dict(name=name, value=value, separator=separator)
759 self.refresh() 776 self.refresh()
760 777
761 expand_form(req.form) 778 expand_form(req.form)
762 rewrite_request(req) 779 rewrite_request(req)
763 780
764 style = self.repo.ui.config("web", "style", "") 781 style = self.config("web", "style", "")
765 if req.form.has_key('style'): 782 if req.form.has_key('style'):
766 style = req.form['style'][0] 783 style = req.form['style'][0]
767 mapfile = style_map(self.templatepath, style) 784 mapfile = style_map(self.templatepath, style)
768 785
769 port = req.env["SERVER_PORT"] 786 port = req.env["SERVER_PORT"]
770 port = port != "80" and (":" + port) or "" 787 port = port != "80" and (":" + port) or ""
771 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port) 788 urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
772 789
773 if not self.reponame: 790 if not self.reponame:
774 self.reponame = (self.repo.ui.config("web", "name") 791 self.reponame = (self.config("web", "name")
775 or req.env.get('REPO_NAME') 792 or req.env.get('REPO_NAME')
776 or req.url.strip('/') or self.repo.root) 793 or req.url.strip('/') or self.repo.root)
777 794
778 self.t = templater.templater(mapfile, templater.common_filters, 795 self.t = templater.templater(mapfile, templater.common_filters,
779 defaults={"url": req.url, 796 defaults={"url": req.url,
983 req.write(z.flush()) 1000 req.write(z.flush())
984 1001
985 def do_archive(self, req): 1002 def do_archive(self, req):
986 changeset = self.repo.lookup(req.form['node'][0]) 1003 changeset = self.repo.lookup(req.form['node'][0])
987 type_ = req.form['type'][0] 1004 type_ = req.form['type'][0]
988 allowed = self.repo.ui.configlist("web", "allow_archive") 1005 allowed = self.configlist("web", "allow_archive")
989 if (type_ in self.archives and (type_ in allowed or 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 self.archive(req, changeset, type_) 1008 self.archive(req, changeset, type_)
992 return 1009 return
993 1010
994 req.write(self.t("error")) 1011 req.write(self.t("error"))
995 1012
996 def do_static(self, req): 1013 def do_static(self, req):
997 fname = req.form['file'][0] 1014 fname = req.form['file'][0]
998 static = self.repo.ui.config("web", "static", 1015 # a repo owner may set web.static in .hg/hgrc to get any file
999 os.path.join(self.templatepath, 1016 # readable by the user running the CGI script
1000 "static")) 1017 static = self.config("web", "static",
1018 os.path.join(self.templatepath, "static"),
1019 untrusted=False)
1001 req.write(staticfile(static, fname, req) 1020 req.write(staticfile(static, fname, req)
1002 or self.t("error", error="%r not found" % fname)) 1021 or self.t("error", error="%r not found" % fname))
1003 1022
1004 def do_capabilities(self, req): 1023 def do_capabilities(self, req):
1005 caps = ['unbundle', 'lookup', 'changegroupsubset'] 1024 caps = ['unbundle', 'lookup', 'changegroupsubset']
1006 if self.repo.ui.configbool('server', 'uncompressed'): 1025 if self.configbool('server', 'uncompressed'):
1007 caps.append('stream=%d' % self.repo.revlogversion) 1026 caps.append('stream=%d' % self.repo.revlogversion)
1008 resp = ' '.join(caps) 1027 resp = ' '.join(caps)
1009 req.httphdr("application/mercurial-0.1", length=len(resp)) 1028 req.httphdr("application/mercurial-0.1", length=len(resp))
1010 req.write(resp) 1029 req.write(resp)
1011 1030
1014 return true if op allowed, else false. 1033 return true if op allowed, else false.
1015 default is policy to use if no config given.''' 1034 default is policy to use if no config given.'''
1016 1035
1017 user = req.env.get('REMOTE_USER') 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 if deny and (not user or deny == ['*'] or user in deny): 1039 if deny and (not user or deny == ['*'] or user in deny):
1021 return False 1040 return False
1022 1041
1023 allow = self.repo.ui.configlist('web', 'allow_' + op) 1042 allow = self.configlist('web', 'allow_' + op)
1024 return (allow and (allow == ['*'] or user in allow)) or default 1043 return (allow and (allow == ['*'] or user in allow)) or default
1025 1044
1026 def do_unbundle(self, req): 1045 def do_unbundle(self, req):
1027 def bail(response, headers={}): 1046 def bail(response, headers={}):
1028 length = int(req.env['CONTENT_LENGTH']) 1047 length = int(req.env['CONTENT_LENGTH'])
1034 req.write('0\n') 1053 req.write('0\n')
1035 req.write(response) 1054 req.write(response)
1036 1055
1037 # require ssl by default, auth info cannot be sniffed and 1056 # require ssl by default, auth info cannot be sniffed and
1038 # replayed 1057 # replayed
1039 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True) 1058 ssl_req = self.configbool('web', 'push_ssl', True)
1040 if ssl_req: 1059 if ssl_req:
1041 if not req.env.get('HTTPS'): 1060 if not req.env.get('HTTPS'):
1042 bail(_('ssl required\n')) 1061 bail(_('ssl required\n'))
1043 return 1062 return
1044 proto = 'https' 1063 proto = 'https'