--- a/mercurial/hgweb.py
+++ b/mercurial/hgweb.py
@@ -34,31 +34,28 @@ def get_mtime(repo_path):
return os.stat(hg_path).st_mtime
def staticfile(directory, fname):
- fname = os.path.realpath(os.path.join(directory, fname))
+ """return a file inside directory with guessed content-type header
+
+ fname always uses '/' as directory separator and isn't allowed to
+ contain unusual path components.
+ Content-type is guessed using the mimetypes module.
+ Return an empty string if fname is illegal or file not found.
+ """
+ parts = fname.split('/')
+ path = directory
+ for part in parts:
+ if (part in ('', os.curdir, os.pardir) or
+ os.sep in part or os.altsep is not None and os.altsep in part):
+ return ""
+ path = os.path.join(path, part)
try:
- # the static dir should be a substring in the real
- # file path, if it is not, we have something strange
- # going on => security breach attempt?
- #
- # This will either:
- # 1) find the `static' path at index 0 = success
- # 2) find the `static' path at other index = error
- # 3) not find the `static' path = ValueError generated
- if fname.index(directory) != 0:
- # generate ValueError manually
- raise ValueError()
-
- os.stat(fname)
-
- ct = mimetypes.guess_type(fname)[0] or "text/plain"
- return "Content-type: %s\n\n%s" % (ct, file(fname).read())
- except ValueError:
- # security breach attempt
+ os.stat(path)
+ ct = mimetypes.guess_type(path)[0] or "text/plain"
+ return "Content-type: %s\n\n%s" % (ct, file(path).read())
+ except (TypeError, OSError):
+ # illegal fname or unreadable file
return ""
- except OSError, e:
- if e.errno == errno.ENOENT:
- return ""
class hgrequest(object):
def __init__(self, inp=None, out=None, env=None):
@@ -739,7 +736,7 @@ class hgweb(object):
def run(self, req=hgrequest()):
def clean(path):
- p = os.path.normpath(path)
+ p = util.normpath(path)
if p[:2] == "..":
raise "suspicious path"
return p
@@ -1001,17 +998,27 @@ def create_server(repo):
class hgwebdir(object):
def __init__(self, config):
def cleannames(items):
- return [(name.strip('/'), path) for name, path in items]
+ return [(name.strip(os.sep), path) for name, path in items]
- if type(config) == type([]):
+ if isinstance(config, (list, tuple)):
self.repos = cleannames(config)
- elif type(config) == type({}):
+ elif isinstance(config, dict):
self.repos = cleannames(config.items())
self.repos.sort()
else:
cp = ConfigParser.SafeConfigParser()
cp.read(config)
- self.repos = cleannames(cp.items("paths"))
+ self.repos = []
+ if cp.has_section('paths'):
+ self.repos.extend(cleannames(cp.items('paths')))
+ if cp.has_section('collections'):
+ for prefix, root in cp.items('collections'):
+ for path in util.walkrepos(root):
+ repo = os.path.normpath(path)
+ name = repo
+ if name.startswith(prefix):
+ name = name[len(prefix):]
+ self.repos.append((name.lstrip(os.sep), repo))
self.repos.sort()
def run(self, req=hgrequest()):