Make hgweb.staticfile() more secure and portable.
authorThomas Arendsen Hein <thomas@intevation.de>
Thu, 02 Mar 2006 09:17:04 +0100
changeset 1825 a9343f9d7365
parent 1824 dca000ef7d52
child 1828 ca82f20b0c19
child 1830 4ced57680ce7
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.
mercurial/hgweb.py
--- a/mercurial/hgweb.py	Wed Mar 01 21:44:00 2006 -0800
+++ b/mercurial/hgweb.py	Thu Mar 02 09:17:04 2006 +0100
@@ -73,31 +73,28 @@
         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):