comparison mercurial/dispatch.py @ 35170:c9740b69b9b7 stable

dispatch: add HGPLAIN=+strictflags to restrict early parsing of global options If this feature is enabled, early options are parsed using the global options table. As the parser stops processing options when non/unknown option is encountered, it won't mistakenly take an option value as a new early option. Still "--" can be injected to terminate the parsing (e.g. "hg -R -- log"), I think it's unlikely to lead to an RCE. To minimize a risk of this change, new fancyopts.earlygetopt() path is enabled only when +strictflags is set. Also the strict parser doesn't support '--repo', a short for '--repository' yet. This limitation will be removed later. As this feature is backward incompatible, I decided to add a new opt-in mechanism to HGPLAIN. I'm not pretty sure if this is the right choice, but I'm thinking of adding +feature/-feature syntax to HGPLAIN. Alternatively, we could add a new environment variable. Any bikeshedding is welcome. Note that HGPLAIN=+strictflags doesn't work correctly in chg session since command arguments are pre-processed in C. This wouldn't be easily fixed.
author Yuya Nishihara <yuya@tcha.org>
date Thu, 23 Nov 2017 22:17:03 +0900
parents 02845f7441af
children aef2b98d9352
comparison
equal deleted inserted replaced
35169:898c6f812a51 35170:c9740b69b9b7
148 ferr = util.stderr 148 ferr = util.stderr
149 149
150 try: 150 try:
151 if not req.ui: 151 if not req.ui:
152 req.ui = uimod.ui.load() 152 req.ui = uimod.ui.load()
153 if req.ui.plain('strictflags'):
154 req.earlyoptions.update(_earlyparseopts(req.args))
153 if _earlyreqoptbool(req, 'traceback', ['--traceback']): 155 if _earlyreqoptbool(req, 'traceback', ['--traceback']):
154 req.ui.setconfig('ui', 'traceback', 'on', '--traceback') 156 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
155 157
156 # set ui streams from the request 158 # set ui streams from the request
157 if req.fin: 159 if req.fin:
641 except (IndexError, ValueError): 643 except (IndexError, ValueError):
642 raise error.Abort(_('malformed --config option: %r ' 644 raise error.Abort(_('malformed --config option: %r '
643 '(use --config section.name=value)') % cfg) 645 '(use --config section.name=value)') % cfg)
644 646
645 return configs 647 return configs
648
649 def _earlyparseopts(args):
650 options = {}
651 fancyopts.fancyopts(args, commands.globalopts, options,
652 gnu=False, early=True)
653 return options
646 654
647 def _earlygetopt(aliases, args, strip=True): 655 def _earlygetopt(aliases, args, strip=True):
648 """Return list of values for an option (or aliases). 656 """Return list of values for an option (or aliases).
649 657
650 The values are listed in the order they appear in args. 658 The values are listed in the order they appear in args.
730 pos += 1 738 pos += 1
731 return values 739 return values
732 740
733 def _earlyreqopt(req, name, aliases): 741 def _earlyreqopt(req, name, aliases):
734 """Peek a list option without using a full options table""" 742 """Peek a list option without using a full options table"""
743 if req.ui.plain('strictflags'):
744 return req.earlyoptions[name]
735 values = _earlygetopt(aliases, req.args, strip=False) 745 values = _earlygetopt(aliases, req.args, strip=False)
736 req.earlyoptions[name] = values 746 req.earlyoptions[name] = values
737 return values 747 return values
738 748
739 def _earlyreqoptstr(req, name, aliases): 749 def _earlyreqoptstr(req, name, aliases):
740 """Peek a string option without using a full options table""" 750 """Peek a string option without using a full options table"""
751 if req.ui.plain('strictflags'):
752 return req.earlyoptions[name]
741 value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1] 753 value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
742 req.earlyoptions[name] = value 754 req.earlyoptions[name] = value
743 return value 755 return value
744 756
745 def _earlyreqoptbool(req, name, aliases): 757 def _earlyreqoptbool(req, name, aliases):
746 """Peek a boolean option without using a full options table 758 """Peek a boolean option without using a full options table
747 759
748 >>> req = request([b'x', b'--debugger']) 760 >>> req = request([b'x', b'--debugger'], uimod.ui())
749 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger']) 761 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
750 True 762 True
751 763
752 >>> req = request([b'x', b'--', b'--debugger']) 764 >>> req = request([b'x', b'--', b'--debugger'], uimod.ui())
753 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger']) 765 >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
754 """ 766 """
767 if req.ui.plain('strictflags'):
768 return req.earlyoptions[name]
755 try: 769 try:
756 argcount = req.args.index("--") 770 argcount = req.args.index("--")
757 except ValueError: 771 except ValueError:
758 argcount = len(req.args) 772 argcount = len(req.args)
759 value = None 773 value = None