Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/dispatch.py @ 34996: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
34995:898c6f812a51 | 34996: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 |