diff 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
line wrap: on
line diff
--- a/mercurial/dispatch.py	Thu Nov 23 22:04:53 2017 +0900
+++ b/mercurial/dispatch.py	Thu Nov 23 22:17:03 2017 +0900
@@ -150,6 +150,8 @@
     try:
         if not req.ui:
             req.ui = uimod.ui.load()
+        if req.ui.plain('strictflags'):
+            req.earlyoptions.update(_earlyparseopts(req.args))
         if _earlyreqoptbool(req, 'traceback', ['--traceback']):
             req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
 
@@ -644,6 +646,12 @@
 
     return configs
 
+def _earlyparseopts(args):
+    options = {}
+    fancyopts.fancyopts(args, commands.globalopts, options,
+                        gnu=False, early=True)
+    return options
+
 def _earlygetopt(aliases, args, strip=True):
     """Return list of values for an option (or aliases).
 
@@ -732,12 +740,16 @@
 
 def _earlyreqopt(req, name, aliases):
     """Peek a list option without using a full options table"""
+    if req.ui.plain('strictflags'):
+        return req.earlyoptions[name]
     values = _earlygetopt(aliases, req.args, strip=False)
     req.earlyoptions[name] = values
     return values
 
 def _earlyreqoptstr(req, name, aliases):
     """Peek a string option without using a full options table"""
+    if req.ui.plain('strictflags'):
+        return req.earlyoptions[name]
     value = (_earlygetopt(aliases, req.args, strip=False) or [''])[-1]
     req.earlyoptions[name] = value
     return value
@@ -745,13 +757,15 @@
 def _earlyreqoptbool(req, name, aliases):
     """Peek a boolean option without using a full options table
 
-    >>> req = request([b'x', b'--debugger'])
+    >>> req = request([b'x', b'--debugger'], uimod.ui())
     >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
     True
 
-    >>> req = request([b'x', b'--', b'--debugger'])
+    >>> req = request([b'x', b'--', b'--debugger'], uimod.ui())
     >>> _earlyreqoptbool(req, b'debugger', [b'--debugger'])
     """
+    if req.ui.plain('strictflags'):
+        return req.earlyoptions[name]
     try:
         argcount = req.args.index("--")
     except ValueError: