comparison mercurial/util.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 23f2736abce3
comparison
equal deleted inserted replaced
13971:bfeaa88b875d 13972:d1f4e7fd970a
14 """ 14 """
15 15
16 from i18n import _ 16 from i18n import _
17 import error, osutil, encoding 17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback 18 import errno, re, shutil, sys, tempfile, traceback
19 import os, stat, time, calendar, textwrap, unicodedata, signal 19 import os, time, calendar, textwrap, unicodedata, signal
20 import imp, socket 20 import imp, socket
21 21
22 # Python compatibility 22 # Python compatibility
23 23
24 def sha1(s): 24 def sha1(s):
490 t = n[-1] 490 t = n[-1]
491 if t in '. ': 491 if t in '. ':
492 return _("filename ends with '%s', which is not allowed " 492 return _("filename ends with '%s', which is not allowed "
493 "on Windows") % t 493 "on Windows") % t
494 494
495 class path_auditor(object):
496 '''ensure that a filesystem path contains no banned components.
497 the following properties of a path are checked:
498
499 - ends with a directory separator
500 - under top-level .hg
501 - starts at the root of a windows drive
502 - contains ".."
503 - traverses a symlink (e.g. a/symlink_here/b)
504 - inside a nested repository (a callback can be used to approve
505 some nested repositories, e.g., subrepositories)
506 '''
507
508 def __init__(self, root, callback=None):
509 self.audited = set()
510 self.auditeddir = set()
511 self.root = root
512 self.callback = callback
513
514 def __call__(self, path):
515 '''Check the relative path.
516 path may contain a pattern (e.g. foodir/**.txt)'''
517
518 if path in self.audited:
519 return
520 # AIX ignores "/" at end of path, others raise EISDIR.
521 if endswithsep(path):
522 raise Abort(_("path ends in directory separator: %s") % path)
523 normpath = os.path.normcase(path)
524 parts = splitpath(normpath)
525 if (os.path.splitdrive(path)[0]
526 or parts[0].lower() in ('.hg', '.hg.', '')
527 or os.pardir in parts):
528 raise Abort(_("path contains illegal component: %s") % path)
529 if '.hg' in path.lower():
530 lparts = [p.lower() for p in parts]
531 for p in '.hg', '.hg.':
532 if p in lparts[1:]:
533 pos = lparts.index(p)
534 base = os.path.join(*parts[:pos])
535 raise Abort(_('path %r is inside nested repo %r')
536 % (path, base))
537
538 parts.pop()
539 prefixes = []
540 while parts:
541 prefix = os.sep.join(parts)
542 if prefix in self.auditeddir:
543 break
544 curpath = os.path.join(self.root, prefix)
545 try:
546 st = os.lstat(curpath)
547 except OSError, err:
548 # EINVAL can be raised as invalid path syntax under win32.
549 # They must be ignored for patterns can be checked too.
550 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
551 raise
552 else:
553 if stat.S_ISLNK(st.st_mode):
554 raise Abort(_('path %r traverses symbolic link %r') %
555 (path, prefix))
556 elif (stat.S_ISDIR(st.st_mode) and
557 os.path.isdir(os.path.join(curpath, '.hg'))):
558 if not self.callback or not self.callback(curpath):
559 raise Abort(_('path %r is inside nested repo %r') %
560 (path, prefix))
561 prefixes.append(prefix)
562 parts.pop()
563
564 self.audited.add(path)
565 # only add prefixes to the cache after checking everything: we don't
566 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
567 self.auditeddir.update(prefixes)
568
569 def lookup_reg(key, name=None, scope=None): 495 def lookup_reg(key, name=None, scope=None):
570 return None 496 return None
571 497
572 def hidewindow(): 498 def hidewindow():
573 """Hide current shell window. 499 """Hide current shell window.