11 This contains helper routines that are independent of the SCM core and hide |
11 This contains helper routines that are independent of the SCM core and hide |
12 platform-specific details from the core. |
12 platform-specific details from the core. |
13 """ |
13 """ |
14 |
14 |
15 from i18n import _ |
15 from i18n import _ |
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile |
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil |
17 import os, threading, time, calendar, ConfigParser, locale, glob |
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob |
18 |
18 |
19 try: |
19 try: |
20 set = set |
20 set = set |
21 frozenset = frozenset |
21 frozenset = frozenset |
22 except NameError: |
22 except NameError: |
364 rootsep = root + os.sep |
364 rootsep = root + os.sep |
365 name = myname |
365 name = myname |
366 if not os.path.isabs(name): |
366 if not os.path.isabs(name): |
367 name = os.path.join(root, cwd, name) |
367 name = os.path.join(root, cwd, name) |
368 name = os.path.normpath(name) |
368 name = os.path.normpath(name) |
|
369 audit_path = path_auditor(root) |
369 if name != rootsep and name.startswith(rootsep): |
370 if name != rootsep and name.startswith(rootsep): |
370 name = name[len(rootsep):] |
371 name = name[len(rootsep):] |
371 audit_path(name) |
372 audit_path(name) |
372 return pconvert(name) |
373 return pconvert(name) |
373 elif name == root: |
374 elif name == root: |
678 hardlink = False |
679 hardlink = False |
679 shutil.copy(src, dst) |
680 shutil.copy(src, dst) |
680 else: |
681 else: |
681 shutil.copy(src, dst) |
682 shutil.copy(src, dst) |
682 |
683 |
683 def audit_path(path): |
684 class path_auditor(object): |
684 """Abort if path contains dangerous components""" |
685 '''ensure that a filesystem path contains no banned components. |
685 parts = os.path.normcase(path).split(os.sep) |
686 the following properties of a path are checked: |
686 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') |
687 |
687 or os.pardir in parts): |
688 - under top-level .hg |
688 raise Abort(_("path contains illegal component: %s") % path) |
689 - starts at the root of a windows drive |
|
690 - contains ".." |
|
691 - traverses a symlink (e.g. a/symlink_here/b) |
|
692 - inside a nested repository''' |
|
693 |
|
694 def __init__(self, root): |
|
695 self.audited = {} |
|
696 self.root = root |
|
697 |
|
698 def __call__(self, path): |
|
699 if path in self.audited: |
|
700 return |
|
701 parts = os.path.normcase(path).split(os.sep) |
|
702 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') |
|
703 or os.pardir in parts): |
|
704 raise Abort(_("path contains illegal component: %s") % path) |
|
705 def check(prefix): |
|
706 curpath = os.path.join(self.root, prefix) |
|
707 try: |
|
708 st = os.lstat(curpath) |
|
709 except OSError, err: |
|
710 if err.errno != errno.ENOENT: |
|
711 raise |
|
712 else: |
|
713 if stat.S_ISLNK(st.st_mode): |
|
714 raise Abort(_('path %r traverses symbolic link %r') % |
|
715 (path, prefix)) |
|
716 if os.path.exists(os.path.join(curpath, '.hg')): |
|
717 raise Abort(_('path %r is inside repo %r') % |
|
718 (path, prefix)) |
|
719 self.audited[prefix] = True |
|
720 for c in strutil.rfindall(path, os.sep): |
|
721 check(path[:c]) |
|
722 self.audited[path] = True |
689 |
723 |
690 def _makelock_file(info, pathname): |
724 def _makelock_file(info, pathname): |
691 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) |
725 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) |
692 os.write(ld, info) |
726 os.write(ld, info) |
693 os.close(ld) |
727 os.close(ld) |
1260 This class is used to hide the details of COW semantics and |
1294 This class is used to hide the details of COW semantics and |
1261 remote file access from higher level code. |
1295 remote file access from higher level code. |
1262 """ |
1296 """ |
1263 def __init__(self, base, audit=True): |
1297 def __init__(self, base, audit=True): |
1264 self.base = base |
1298 self.base = base |
1265 self.audit = audit |
1299 if audit: |
|
1300 self.audit_path = path_auditor(base) |
|
1301 else: |
|
1302 self.audit_path = always |
1266 |
1303 |
1267 def __getattr__(self, name): |
1304 def __getattr__(self, name): |
1268 if name == '_can_symlink': |
1305 if name == '_can_symlink': |
1269 self._can_symlink = checklink(self.base) |
1306 self._can_symlink = checklink(self.base) |
1270 return self._can_symlink |
1307 return self._can_symlink |
1271 raise AttributeError(name) |
1308 raise AttributeError(name) |
1272 |
1309 |
1273 def __call__(self, path, mode="r", text=False, atomictemp=False): |
1310 def __call__(self, path, mode="r", text=False, atomictemp=False): |
1274 if self.audit: |
1311 self.audit_path(path) |
1275 audit_path(path) |
|
1276 f = os.path.join(self.base, path) |
1312 f = os.path.join(self.base, path) |
1277 |
1313 |
1278 if not text and "b" not in mode: |
1314 if not text and "b" not in mode: |
1279 mode += "b" # for that other OS |
1315 mode += "b" # for that other OS |
1280 |
1316 |
1291 if nlink > 1: |
1327 if nlink > 1: |
1292 rename(mktempcopy(f), f) |
1328 rename(mktempcopy(f), f) |
1293 return posixfile(f, mode) |
1329 return posixfile(f, mode) |
1294 |
1330 |
1295 def symlink(self, src, dst): |
1331 def symlink(self, src, dst): |
1296 if self.audit: |
1332 self.audit_path(dst) |
1297 audit_path(dst) |
|
1298 linkname = os.path.join(self.base, dst) |
1333 linkname = os.path.join(self.base, dst) |
1299 try: |
1334 try: |
1300 os.unlink(linkname) |
1335 os.unlink(linkname) |
1301 except OSError: |
1336 except OSError: |
1302 pass |
1337 pass |