comparison mercurial/hgweb.py @ 1825:a9343f9d7365

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.
author Thomas Arendsen Hein <thomas@intevation.de>
date Thu, 02 Mar 2006 09:17:04 +0100
parents a373881fdf2a
children 4ced57680ce7
comparison
equal deleted inserted replaced
1824:dca000ef7d52 1825:a9343f9d7365
71 return os.stat(cl_path).st_mtime 71 return os.stat(cl_path).st_mtime
72 else: 72 else:
73 return os.stat(hg_path).st_mtime 73 return os.stat(hg_path).st_mtime
74 74
75 def staticfile(directory, fname): 75 def staticfile(directory, fname):
76 fname = os.path.realpath(os.path.join(directory, fname)) 76 """return a file inside directory with guessed content-type header
77 77
78 fname always uses '/' as directory separator and isn't allowed to
79 contain unusual path components.
80 Content-type is guessed using the mimetypes module.
81 Return an empty string if fname is illegal or file not found.
82
83 """
84 parts = fname.split('/')
85 path = directory
86 for part in parts:
87 if (part in ('', os.curdir, os.pardir) or
88 os.sep in part or os.altsep is not None and os.altsep in part):
89 return ""
90 path = os.path.join(path, part)
78 try: 91 try:
79 # the static dir should be a substring in the real 92 os.stat(path)
80 # file path, if it is not, we have something strange 93 ct = mimetypes.guess_type(path)[0] or "text/plain"
81 # going on => security breach attempt? 94 return "Content-type: %s\n\n%s" % (ct, file(path).read())
82 # 95 except (TypeError, OSError):
83 # This will either: 96 # illegal fname or unreadable file
84 # 1) find the `static' path at index 0 = success
85 # 2) find the `static' path at other index = error
86 # 3) not find the `static' path = ValueError generated
87 if fname.index(directory) != 0:
88 # generate ValueError manually
89 raise ValueError()
90
91 os.stat(fname)
92
93 ct = mimetypes.guess_type(fname)[0] or "text/plain"
94 return "Content-type: %s\n\n%s" % (ct, file(fname).read())
95 except ValueError:
96 # security breach attempt
97 return "" 97 return ""
98 except OSError, e:
99 if e.errno == errno.ENOENT:
100 return ""
101 98
102 class hgrequest(object): 99 class hgrequest(object):
103 def __init__(self, inp=None, out=None, env=None): 100 def __init__(self, inp=None, out=None, env=None):
104 self.inp = inp or sys.stdin 101 self.inp = inp or sys.stdin
105 self.out = out or sys.stdout 102 self.out = out or sys.stdout