path_auditor: check filenames for basic platform validity (issue2755)
authorAdrian Buehlmann <adrian@cadifra.com>
Wed, 06 Apr 2011 18:09:43 +0200
changeset 13916 98ee3dd5bab4
parent 13915 8f81d6f4047f
child 13917 3259a067c102
path_auditor: check filenames for basic platform validity (issue2755) Example (on Windows): $ hg parents $ hg manifest tip con.xml $ hg update abort: filename contains 'con', which is reserved on Windows: con.xml Before this patch, update produced (as explained in issue2755): $ hg update abort: No usable temporary filename found I've added the new function checkwinfilename to util.py and not to windows.py, so that we can later call it when running on posix platforms too, for when we decide to implement a (configurable) warning message on 'hg add'. As per this patch, checkwinfilename is currently only used when running on Windwows. path_auditor calls checkosfilename, which is a NOP on posix and an alias for checkwinfilename on Windows.
mercurial/posix.py
mercurial/util.py
--- a/mercurial/posix.py	Fri Apr 08 17:47:58 2011 +0300
+++ b/mercurial/posix.py	Wed Apr 06 18:09:43 2011 +0200
@@ -147,6 +147,11 @@
     except (OSError, AttributeError):
         return False
 
+def checkosfilename(path):
+    '''Check that the base-relative path is a valid filename on this platform.
+    Returns None if the path is ok, or a UI string describing the problem.'''
+    pass # on posix platforms, every path is ok
+
 def set_binary(fd):
     pass
 
--- a/mercurial/util.py	Fri Apr 08 17:47:58 2011 +0300
+++ b/mercurial/util.py	Wed Apr 06 18:09:43 2011 +0200
@@ -493,6 +493,48 @@
 
     return hardlink, num
 
+_windows_reserved_filenames = '''con prn aux nul
+    com1 com2 com3 com4 com5 com6 com7 com8 com9
+    lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
+_windows_reserved_chars = ':*?"<>|'
+def checkwinfilename(path):
+    '''Check that the base-relative path is a valid filename on Windows.
+    Returns None if the path is ok, or a UI string describing the problem.
+
+    >>> checkwinfilename("just/a/normal/path")
+    >>> checkwinfilename("foo/bar/con.xml")
+    "filename contains 'con', which is reserved on Windows"
+    >>> checkwinfilename("foo/con.xml/bar")
+    "filename contains 'con', which is reserved on Windows"
+    >>> checkwinfilename("foo/bar/xml.con")
+    >>> checkwinfilename("foo/bar/AUX/bla.txt")
+    "filename contains 'AUX', which is reserved on Windows"
+    >>> checkwinfilename("foo/bar/bla:.txt")
+    "filename contains ':', which is reserved on Windows"
+    >>> checkwinfilename("foo/bar/b\07la.txt")
+    "filename contains '\\x07', which is invalid on Windows"
+    >>> checkwinfilename("foo/bar/bla ")
+    "filename ends with ' ', which is not allowed on Windows"
+    '''
+    for n in path.replace('\\', '/').split('/'):
+        if not n:
+            continue
+        for c in n:
+            if c in _windows_reserved_chars:
+                return _("filename contains '%s', which is reserved "
+                         "on Windows") % c
+            if ord(c) <= 31:
+                return _("filename contains '%s', which is invalid "
+                         "on Windows") % c
+        base = n.split('.')[0]
+        if base and base.lower() in _windows_reserved_filenames:
+            return _("filename contains '%s', which is reserved "
+                     "on Windows") % base
+        t = n[-1]
+        if t in '. ':
+            return _("filename ends with '%s', which is not allowed "
+                     "on Windows") % t
+
 class path_auditor(object):
     '''ensure that a filesystem path contains no banned components.
     the following properties of a path are checked:
@@ -560,6 +602,9 @@
             prefixes.append(prefix)
             parts.pop()
 
+        r = checkosfilename(path)
+        if r:
+            raise Abort("%s: %s" % (r, path))
         self.audited.add(path)
         # only add prefixes to the cache after checking everything: we don't
         # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
@@ -577,6 +622,7 @@
     pass
 
 if os.name == 'nt':
+    checkosfilename = checkwinfilename
     from windows import *
 else:
     from posix import *