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 |