##// END OF EJS Templates
dispatch: protect against malicious 'hg serve --stdio' invocations (sec)...
dispatch: protect against malicious 'hg serve --stdio' invocations (sec) Some shared-ssh installations assume that 'hg serve --stdio' is a safe command to run for minimally trusted users. Unfortunately, the messy implementation of argument parsing here meant that trying to access a repo named '--debugger' would give the user a pdb prompt, thereby sidestepping any hoped-for sandboxing. Serving repositories over HTTP(S) is unaffected. We're not currently hardening any subcommands other than 'serve'. If your service exposes other commands to users with arbitrary repository names, it is imperative that you defend against repository names of '--debugger' and anything starting with '--config'. The read-only mode of hg-ssh stopped working because it provided its hook configuration to "hg serve --stdio" via --config parameter. This is banned for security reasons now. This patch switches it to directly call ui.setconfig(). If your custom hosting infrastructure relies on passing --config to "hg serve --stdio", you'll need to find a different way to get that configuration into Mercurial, either by using ui.setconfig() as hg-ssh does in this patch, or by placing an hgrc file someplace where Mercurial will read it. mitrandir@fb.com provided some extra fixes for the dispatch code and for hg-ssh in places that I overlooked.

File last commit:

r31747:aff7b32b default
r32050:77eaf953 4.1.3 stable
Show More
hook.py
266 lines | 9.4 KiB | text/x-python | PythonLexer
Matt Mackall
hooks: separate hook code into a separate module
r4622 # hook.py - hook support for mercurial
#
# Copyright 2007 Matt Mackall <mpm@selenic.com>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Matt Mackall
hooks: separate hook code into a separate module
r4622
Gregory Szorc
hook: use absolute_import
r25953 from __future__ import absolute_import
import os
import sys
import time
from .i18n import _
from . import (
demandimport,
error,
extensions,
Pulkit Goyal
py3: use pycompat.getcwd() instead of os.getcwd()...
r30519 pycompat,
Gregory Szorc
hook: use absolute_import
r25953 util,
)
Matt Mackall
hooks: separate hook code into a separate module
r4622
def _pythonhook(ui, repo, name, hname, funcname, args, throw):
'''call python hook. hook is callable object, looked up as
name in python module. if callable returns "true", hook
fails, else passes. if hook raises exception, treated as
hook failure. exception propagates if throw is "true".
reason for "true" meaning "hook failed" is so that
unmodified commands (e.g. mercurial.commands.update) can
be run as hooks without wrappers to convert return values.'''
Augie Fackler
hook: restore use of callable() since it was readded in Python 3.2
r21797 if callable(funcname):
Mads Kiilerich
hooks: for python hooks, consistently use __name__ etc as name, not the repr...
r20548 obj = funcname
funcname = obj.__module__ + "." + obj.__name__
else:
Matt Mackall
hooks: separate hook code into a separate module
r4622 d = funcname.rfind('.')
if d == -1:
Siddharth Agarwal
hook: raise a separate exception for when loading a hook fails...
r26692 raise error.HookLoadError(
Siddharth Agarwal
hook: even fewer parentheses for load errors...
r28106 _('%s hook is invalid: "%s" not in a module')
Siddharth Agarwal
hook: raise a separate exception for when loading a hook fails...
r26692 % (hname, funcname))
Matt Mackall
hooks: separate hook code into a separate module
r4622 modname = funcname[:d]
Sune Foldager
hook: fix bug (reuse of variable) introduced in 872d49dd577a...
r10103 oldpaths = sys.path
Augie Fackler
windows: check util.mainfrozen() instead of ad-hoc checks everywhere
r14941 if util.mainfrozen():
Steve Borho
hook: fix full path imports on Windows (issue1779)...
r9332 # binary installs require sys.path manipulation
Sune Foldager
hook: fix bug (reuse of variable) introduced in 872d49dd577a...
r10103 modpath, modfile = os.path.split(modname)
if modpath and modfile:
sys.path = sys.path[:] + [modpath]
modname = modfile
Jordi Gutiérrez Hermoso
hooks: replace if-try-finally with a "with" statement...
r25328 with demandimport.deactivated():
Matt Mackall
hooks: separate hook code into a separate module
r4622 try:
Jordi Gutiérrez Hermoso
hooks: replace if-try-finally with a "with" statement...
r25328 obj = __import__(modname)
Siddharth Agarwal
hook: don't crash on syntax errors in python hooks...
r28109 except (ImportError, SyntaxError):
Siddharth Agarwal
hook: use sys.exc_info rather than the deprecated equivalents...
r28078 e1 = sys.exc_info()
Jordi Gutiérrez Hermoso
hooks: replace if-try-finally with a "with" statement...
r25328 try:
# extensions are loaded with hgext_ prefix
obj = __import__("hgext_%s" % modname)
Siddharth Agarwal
hook: don't crash on syntax errors in python hooks...
r28109 except (ImportError, SyntaxError):
Siddharth Agarwal
hook: use sys.exc_info rather than the deprecated equivalents...
r28078 e2 = sys.exc_info()
Jordi Gutiérrez Hermoso
hooks: replace if-try-finally with a "with" statement...
r25328 if ui.tracebackflag:
ui.warn(_('exception from first failed import '
'attempt:\n'))
ui.traceback(e1)
if ui.tracebackflag:
ui.warn(_('exception from second failed import '
'attempt:\n'))
ui.traceback(e2)
Siddharth Agarwal
hook: for python hook ImportErrors, add note to run with --traceback...
r28080
if not ui.tracebackflag:
tracebackhint = _(
'run with --traceback for stack trace')
else:
tracebackhint = None
Siddharth Agarwal
hook: raise a separate exception for when loading a hook fails...
r26692 raise error.HookLoadError(
Siddharth Agarwal
hook: fewer parentheses for hook load errors...
r28079 _('%s hook is invalid: import of "%s" failed') %
Siddharth Agarwal
hook: for python hook ImportErrors, add note to run with --traceback...
r28080 (hname, modname), hint=tracebackhint)
Steve Borho
hook: fix full path imports on Windows (issue1779)...
r9332 sys.path = oldpaths
Matt Mackall
hooks: separate hook code into a separate module
r4622 try:
for p in funcname.split('.')[1:]:
obj = getattr(obj, p)
Benoit Boissinot
remove unused variables
r7280 except AttributeError:
Siddharth Agarwal
hook: raise a separate exception for when loading a hook fails...
r26692 raise error.HookLoadError(
Siddharth Agarwal
hook: fewer parentheses for hook load errors...
r28079 _('%s hook is invalid: "%s" is not defined')
Siddharth Agarwal
hook: raise a separate exception for when loading a hook fails...
r26692 % (hname, funcname))
Augie Fackler
hook: restore use of callable() since it was readded in Python 3.2
r21797 if not callable(obj):
Siddharth Agarwal
hook: raise a separate exception for when loading a hook fails...
r26692 raise error.HookLoadError(
Siddharth Agarwal
hook: fewer parentheses for hook load errors...
r28079 _('%s hook is invalid: "%s" is not callable')
Siddharth Agarwal
hook: raise a separate exception for when loading a hook fails...
r26692 % (hname, funcname))
Mads Kiilerich
hooks: move logging of hook name to after we have found the hook...
r20547
ui.note(_("calling hook %s: %s\n") % (hname, funcname))
starttime = time.time()
Matt Mackall
hooks: separate hook code into a separate module
r4622 try:
Matt Mackall
hooks: use try/except/finally
r25084 r = obj(ui=ui, repo=repo, hooktype=name, **args)
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except Exception as exc:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 if isinstance(exc, error.Abort):
Matt Mackall
hooks: use try/except/finally
r25084 ui.warn(_('error: %s hook failed: %s\n') %
(hname, exc.args[0]))
else:
ui.warn(_('error: %s hook raised an exception: '
'%s\n') % (hname, exc))
if throw:
Matt Mackall
hooks: separate hook code into a separate module
r4622 raise
Siddharth Agarwal
hook: for python hook exceptions, add note to run with --traceback...
r28108 if not ui.tracebackflag:
ui.warn(_('(run with --traceback for stack trace)\n'))
Matt Mackall
hooks: use try/except/finally
r25084 ui.traceback()
Siddharth Agarwal
hook: for python hooks, also return whether an exception was raised...
r26739 return True, True
Idan Kamara
hooks: redirect stdout/err/in to the ui descriptors when calling python hooks...
r14889 finally:
Durham Goode
blackbox: logs python and extension hooks via ui.log()...
r18671 duration = time.time() - starttime
Durham Goode
blackbox: do not translate the log messages...
r18691 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
Mads Kiilerich
hooks: for python hooks, consistently use __name__ etc as name, not the repr...
r20548 name, funcname, duration)
Matt Mackall
hooks: separate hook code into a separate module
r4622 if r:
if throw:
Pierre-Yves David
hook: raise a more specialized HookAbort exception when a hook fails...
r23415 raise error.HookAbort(_('%s hook failed') % hname)
Matt Mackall
hooks: separate hook code into a separate module
r4622 ui.warn(_('warning: %s hook failed\n') % hname)
Siddharth Agarwal
hook: for python hooks, also return whether an exception was raised...
r26739 return r, False
Matt Mackall
hooks: separate hook code into a separate module
r4622
def _exthook(ui, repo, name, cmd, args, throw):
ui.note(_("running hook %s: %s\n") % (name, cmd))
Matt Mackall
Introduce HG_PREPEND to solve pretxn races...
r7787
Durham Goode
blackbox: logs python and extension hooks via ui.log()...
r18671 starttime = time.time()
Matt Mackall
Introduce HG_PREPEND to solve pretxn races...
r7787 env = {}
FUJIWARA Katsunori
hook: centralize passing HG_PENDING to external hook process...
r26751
# make in-memory changes visible to external process
Sietse Brouwer
dirstate: don't write repo.currenttransaction to repo.dirstate if repo...
r27228 if repo is not None:
tr = repo.currenttransaction()
repo.dirstate.write(tr)
if tr and tr.writepending():
env['HG_PENDING'] = repo.root
FUJIWARA Katsunori
hook: centralize passing HG_PENDING to external hook process...
r26751
Matt Mackall
Introduce HG_PREPEND to solve pretxn races...
r7787 for k, v in args.iteritems():
Augie Fackler
hook: restore use of callable() since it was readded in Python 3.2
r21797 if callable(v):
Matt Mackall
Introduce HG_PREPEND to solve pretxn races...
r7787 v = v()
Dan Villiom Podlaski Christiansen
hooks: sort any dictionaries set in the environment...
r13207 if isinstance(v, dict):
# make the dictionary element order stable across Python
# implementations
v = ('{' +
', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
'}')
Matt Mackall
Introduce HG_PREPEND to solve pretxn races...
r7787 env['HG_' + k.upper()] = v
Matt Mackall
hooks: fix pre- and post- hooks specified in .hg/hgrc...
r5869 if repo:
cwd = repo.root
else:
Pulkit Goyal
py3: use pycompat.getcwd() instead of os.getcwd()...
r30519 cwd = pycompat.getcwd()
Yuya Nishihara
util.system: use ui.system() in place of optional ui.fout parameter
r23270 r = ui.system(cmd, environ=env, cwd=cwd)
Durham Goode
blackbox: logs python and extension hooks via ui.log()...
r18671
duration = time.time() - starttime
Durham Goode
blackbox: do not translate the log messages...
r18691 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
Durham Goode
blackbox: logs python and extension hooks via ui.log()...
r18671 name, cmd, duration)
Matt Mackall
hooks: separate hook code into a separate module
r4622 if r:
Adrian Buehlmann
rename explain_exit to explainexit
r14234 desc, r = util.explainexit(r)
Matt Mackall
hooks: separate hook code into a separate module
r4622 if throw:
Pierre-Yves David
hook: raise a more specialized HookAbort exception when a hook fails...
r23415 raise error.HookAbort(_('%s hook %s') % (name, desc))
Matt Mackall
hooks: separate hook code into a separate module
r4622 ui.warn(_('warning: %s hook %s\n') % (name, desc))
return r
Pierre-Yves David
hook: report untrusted hooks as failure (issue5110) (BC)...
r28938 # represent an untrusted hook command
_fromuntrusted = object()
Matt Zuba
hooks: prioritize run order of hooks...
r15896 def _allhooks(ui):
Pierre-Yves David
hook: split config reading further...
r28937 """return a list of (hook-id, cmd) pairs sorted by priority"""
hooks = _hookitems(ui)
Pierre-Yves David
hook: report untrusted hooks as failure (issue5110) (BC)...
r28938 # Be careful in this section, propagating the real commands from untrusted
# sources would create a security vulnerability, make sure anything altered
# in that section uses "_fromuntrusted" as its command.
untrustedhooks = _hookitems(ui, _untrusted=True)
for name, value in untrustedhooks.items():
trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
if value != trustedvalue:
(lp, lo, lk, lv) = trustedvalue
hooks[name] = (lp, lo, lk, _fromuntrusted)
# (end of the security sensitive section)
Pierre-Yves David
hook: split config reading further...
r28937 return [(k, v) for p, o, k, v in sorted(hooks.values())]
Pierre-Yves David
hook: report untrusted hooks as failure (issue5110) (BC)...
r28938 def _hookitems(ui, _untrusted=False):
Pierre-Yves David
hook: split config reading further...
r28937 """return all hooks items ready to be sorted"""
Pierre-Yves David
hook: small refactor to store hooks as dict instead of list...
r28936 hooks = {}
Pierre-Yves David
hook: report untrusted hooks as failure (issue5110) (BC)...
r28938 for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
Matt Zuba
hooks: prioritize run order of hooks...
r15896 if not name.startswith('priority'):
priority = ui.configint('hooks', 'priority.%s' % name, 0)
Pierre-Yves David
hook: small refactor to store hooks as dict instead of list...
r28936 hooks[name] = (-priority, len(hooks), name, cmd)
Pierre-Yves David
hook: split config reading further...
r28937 return hooks
Matt Zuba
hooks: prioritize run order of hooks...
r15896
Matt Mackall
hook: redirect stdout to stderr for ssh and http servers
r5833 _redirect = False
def redirect(state):
Alexis S. L. Carvalho
hook.py: fix redirections introduced by 323b9c55b328...
r6266 global _redirect
Matt Mackall
hook: redirect stdout to stderr for ssh and http servers
r5833 _redirect = state
Matt Mackall
hooks: separate hook code into a separate module
r4622 def hook(ui, repo, name, throw=False, **args):
Idan Kamara
ui: add a variable to control whether hooks should be called...
r17048 if not ui.callhooks:
return False
Siddharth Agarwal
hook: factor out determination of hooks from running them...
r26737 hooks = []
for hname, cmd in _allhooks(ui):
if hname.split('.')[0] == name and cmd:
hooks.append((hname, cmd))
Siddharth Agarwal
hook.runhooks: return a dict of result values...
r26738 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
r = False
for hname, cmd in hooks:
Siddharth Agarwal
hook: for python hooks, also return whether an exception was raised...
r26739 r = res[hname][0] or r
Siddharth Agarwal
hook.runhooks: return a dict of result values...
r26738 return r
Siddharth Agarwal
hook: factor out determination of hooks from running them...
r26737
def runhooks(ui, repo, name, hooks, throw=False, **args):
Siddharth Agarwal
hook.runhooks: return a dict of result values...
r26738 res = {}
Sune Foldager
hook: only redirect stdout if it and stderr are valid files...
r9658 oldstdout = -1
Matt Mackall
hook: redirect stdout to stderr for ssh and http servers
r5833
Jesse Long
hooks: restore io correctly on exception
r7416 try:
Siddharth Agarwal
hook: factor out determination of hooks from running them...
r26737 for hname, cmd in hooks:
Matt Mackall
hooks: delay I/O redirection until we actually run a hook (issue3711)...
r17963 if oldstdout == -1 and _redirect:
try:
Yuya Nishihara
py3: bulk replace sys.stdin/out/err by util's...
r30473 stdoutno = util.stdout.fileno()
stderrno = util.stderr.fileno()
Matt Mackall
hooks: delay I/O redirection until we actually run a hook (issue3711)...
r17963 # temporarily redirect stdout to stderr, if possible
if stdoutno >= 0 and stderrno >= 0:
Yuya Nishihara
py3: bulk replace sys.stdin/out/err by util's...
r30473 util.stdout.flush()
Matt Mackall
hooks: delay I/O redirection until we actually run a hook (issue3711)...
r17963 oldstdout = os.dup(stdoutno)
os.dup2(stderrno, stdoutno)
Matt Mackall
hooks: be even more forgiven of non-fd descriptors (issue3711)...
r17964 except (OSError, AttributeError):
# files seem to be bogus, give up on redirecting (WSGI, etc)
Matt Mackall
hooks: delay I/O redirection until we actually run a hook (issue3711)...
r17963 pass
Pierre-Yves David
hook: report untrusted hooks as failure (issue5110) (BC)...
r28938 if cmd is _fromuntrusted:
if throw:
raise error.HookAbort(
_('untrusted hook %s not executed') % name,
hint = _("see 'hg help config.trusted'"))
ui.warn(_('warning: untrusted hook %s not executed\n') % name)
r = 1
raised = False
elif callable(cmd):
Siddharth Agarwal
hook: for python hooks, also return whether an exception was raised...
r26739 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
Jesse Long
hooks: restore io correctly on exception
r7416 elif cmd.startswith('python:'):
Steve Borho
hook: fix full path imports on Windows (issue1779)...
r9332 if cmd.count(':') >= 2:
path, cmd = cmd[7:].rsplit(':', 1)
Alexander Solovyov
hook: assume relative path to hook is given from repo root
r13118 path = util.expandpath(path)
Matt Mackall
hook: fix import path handling for repo=None
r13119 if repo:
path = os.path.join(repo.root, path)
Simon Heimberg
hooks: print out more information when loading a python hook fails...
r17217 try:
mod = extensions.loadpath(path, 'hghook.%s' % hname)
except Exception:
ui.write(_("loading %s hook failed:\n") % hname)
raise
Alexander Solovyov
ability to load hooks from arbitrary python module
r7916 hookfn = getattr(mod, cmd)
else:
hookfn = cmd[7:].strip()
Siddharth Agarwal
hook: for python hooks, also return whether an exception was raised...
r26739 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
throw)
Jesse Long
hooks: restore io correctly on exception
r7416 else:
Siddharth Agarwal
hook.runhooks: return a dict of result values...
r26738 r = _exthook(ui, repo, hname, cmd, args, throw)
Siddharth Agarwal
hook: for python hooks, also return whether an exception was raised...
r26739 raised = False
Siddharth Agarwal
hook.runhooks: return a dict of result values...
r26738
Siddharth Agarwal
hook: for python hooks, also return whether an exception was raised...
r26739 res[hname] = r, raised
Matt Harbison
hook: forcibly flush stderr for Windows test stability...
r24716
# The stderr is fully buffered on Windows when connected to a pipe.
# A forcible flush is required to make small stderr data in the
# remote side available to the client immediately.
Yuya Nishihara
py3: bulk replace sys.stdin/out/err by util's...
r30473 util.stderr.flush()
Jesse Long
hooks: restore io correctly on exception
r7416 finally:
Sune Foldager
hook: only redirect stdout if it and stderr are valid files...
r9658 if _redirect and oldstdout >= 0:
Yuya Nishihara
py3: bulk replace sys.stdin/out/err by util's...
r30473 util.stdout.flush() # write hook output to stderr fd
Sune Foldager
hook: only redirect stdout if it and stderr are valid files...
r9658 os.dup2(oldstdout, stdoutno)
Jesse Long
hooks: restore io correctly on exception
r7416 os.close(oldstdout)
Alexis S. L. Carvalho
hook.py: fix redirections introduced by 323b9c55b328...
r6266
Siddharth Agarwal
hook.runhooks: return a dict of result values...
r26738 return res