# HG changeset patch # User Thomas Arendsen Hein # Date 1141287424 -3600 # Node ID a9343f9d7365ebdf114501ce09acc76fd8294751 # Parent dca000ef7d52402cfec550fde5ac95430640cce5 Make hgweb.staticfile() more secure and portable. Without this, files in directories next to the static directory starting with 'static' could be retrieved, e.g. with '../static.private/foo'. Additionally staticfile now generates platform specific pathnames from the /-separated paths given in the URL. Illegal file names (e.g. containing %00) now yield a sane error message. diff --git a/mercurial/hgweb.py b/mercurial/hgweb.py --- a/mercurial/hgweb.py +++ b/mercurial/hgweb.py @@ -73,31 +73,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):