comparison mercurial/scmutil.py @ 13972:d1f4e7fd970a

move path_auditor from util to scmutil
author Adrian Buehlmann <adrian@cadifra.com>
date Wed, 20 Apr 2011 22:43:31 +0200
parents bfeaa88b875d
children 366fa83f9820
comparison
equal deleted inserted replaced
13971:bfeaa88b875d 13972:d1f4e7fd970a
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from i18n import _ 8 from i18n import _
9 import util, error 9 import util, error
10 import os, errno 10 import os, errno, stat
11 11
12 def checkportable(ui, f): 12 def checkportable(ui, f):
13 '''Check if filename f is portable and warn or abort depending on config''' 13 '''Check if filename f is portable and warn or abort depending on config'''
14 util.checkfilename(f) 14 util.checkfilename(f)
15 val = ui.config('ui', 'portablefilenames', 'warn') 15 val = ui.config('ui', 'portablefilenames', 'warn')
24 ui.warn(_("warning: %s: %r\n") % (msg, f)) 24 ui.warn(_("warning: %s: %r\n") % (msg, f))
25 elif bval is None and lval != 'ignore': 25 elif bval is None and lval != 'ignore':
26 raise error.ConfigError( 26 raise error.ConfigError(
27 _("ui.portablefilenames value is invalid ('%s')") % val) 27 _("ui.portablefilenames value is invalid ('%s')") % val)
28 28
29 class path_auditor(object):
30 '''ensure that a filesystem path contains no banned components.
31 the following properties of a path are checked:
32
33 - ends with a directory separator
34 - under top-level .hg
35 - starts at the root of a windows drive
36 - contains ".."
37 - traverses a symlink (e.g. a/symlink_here/b)
38 - inside a nested repository (a callback can be used to approve
39 some nested repositories, e.g., subrepositories)
40 '''
41
42 def __init__(self, root, callback=None):
43 self.audited = set()
44 self.auditeddir = set()
45 self.root = root
46 self.callback = callback
47
48 def __call__(self, path):
49 '''Check the relative path.
50 path may contain a pattern (e.g. foodir/**.txt)'''
51
52 if path in self.audited:
53 return
54 # AIX ignores "/" at end of path, others raise EISDIR.
55 if util.endswithsep(path):
56 raise util.Abort(_("path ends in directory separator: %s") % path)
57 normpath = os.path.normcase(path)
58 parts = util.splitpath(normpath)
59 if (os.path.splitdrive(path)[0]
60 or parts[0].lower() in ('.hg', '.hg.', '')
61 or os.pardir in parts):
62 raise util.Abort(_("path contains illegal component: %s") % path)
63 if '.hg' in path.lower():
64 lparts = [p.lower() for p in parts]
65 for p in '.hg', '.hg.':
66 if p in lparts[1:]:
67 pos = lparts.index(p)
68 base = os.path.join(*parts[:pos])
69 raise util.Abort(_('path %r is inside nested repo %r')
70 % (path, base))
71
72 parts.pop()
73 prefixes = []
74 while parts:
75 prefix = os.sep.join(parts)
76 if prefix in self.auditeddir:
77 break
78 curpath = os.path.join(self.root, prefix)
79 try:
80 st = os.lstat(curpath)
81 except OSError, err:
82 # EINVAL can be raised as invalid path syntax under win32.
83 # They must be ignored for patterns can be checked too.
84 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
85 raise
86 else:
87 if stat.S_ISLNK(st.st_mode):
88 raise util.Abort(
89 _('path %r traverses symbolic link %r')
90 % (path, prefix))
91 elif (stat.S_ISDIR(st.st_mode) and
92 os.path.isdir(os.path.join(curpath, '.hg'))):
93 if not self.callback or not self.callback(curpath):
94 raise util.Abort(_('path %r is inside nested repo %r') %
95 (path, prefix))
96 prefixes.append(prefix)
97 parts.pop()
98
99 self.audited.add(path)
100 # only add prefixes to the cache after checking everything: we don't
101 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
102 self.auditeddir.update(prefixes)
103
29 class opener(object): 104 class opener(object):
30 '''Open files relative to a base directory 105 '''Open files relative to a base directory
31 106
32 This class is used to hide the details of COW semantics and 107 This class is used to hide the details of COW semantics and
33 remote file access from higher level code. 108 remote file access from higher level code.
34 ''' 109 '''
35 def __init__(self, base, audit=True): 110 def __init__(self, base, audit=True):
36 self.base = base 111 self.base = base
37 if audit: 112 if audit:
38 self.auditor = util.path_auditor(base) 113 self.auditor = path_auditor(base)
39 else: 114 else:
40 self.auditor = util.always 115 self.auditor = util.always
41 self.createmode = None 116 self.createmode = None
42 self._trustnlink = None 117 self._trustnlink = None
43 118
130 name = myname 205 name = myname
131 if not os.path.isabs(name): 206 if not os.path.isabs(name):
132 name = os.path.join(root, cwd, name) 207 name = os.path.join(root, cwd, name)
133 name = os.path.normpath(name) 208 name = os.path.normpath(name)
134 if auditor is None: 209 if auditor is None:
135 auditor = util.path_auditor(root) 210 auditor = path_auditor(root)
136 if name != rootsep and name.startswith(rootsep): 211 if name != rootsep and name.startswith(rootsep):
137 name = name[len(rootsep):] 212 name = name[len(rootsep):]
138 auditor(name) 213 auditor(name)
139 return util.pconvert(name) 214 return util.pconvert(name)
140 elif name == root: 215 elif name == root: