@@ -353,7 +353,8 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
353353 commands to be run or, if -t is specified, transcript files to run.
354354 This should be set to False if your application parses its own arguments.
355355 :param transcript_files: allow running transcript tests when allow_cli_args is False
356- :param allow_redirection: should output redirection and pipes be allowed
356+ :param allow_redirection: should output redirection and pipes be allowed. this is only a security setting
357+ and does not alter parsing behavior.
357358 :param multiline_commands: list of commands allowed to accept multi-line input
358359 :param terminators: list of characters that terminate a command. These are mainly intended for terminating
359360 multiline commands, but will also terminate single-line commands. If not supplied, then
@@ -376,11 +377,14 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
376377 # Call super class constructor
377378 super ().__init__ (completekey = completekey , stdin = stdin , stdout = stdout )
378379
379- # Attributes which should NOT be dynamically settable at runtime
380+ # Attributes which should NOT be dynamically settable via the set command at runtime
381+ # To prevent a user from altering these with the py/ipy commands, remove locals_in_py from the
382+ # settable dictionary during your applications's __init__ method.
380383 self .default_to_shell = False # Attempt to run unrecognized commands as shell commands
381384 self .quit_on_sigint = False # Quit the loop on interrupt instead of just resetting prompt
385+ self .allow_redirection = allow_redirection # Security setting to prevent redirection of stdout
382386
383- # Attributes which ARE dynamically settable at runtime
387+ # Attributes which ARE dynamically settable via the set command at runtime
384388 self .continuation_prompt = '> '
385389 self .debug = False
386390 self .echo = False
@@ -440,8 +444,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
440444 # True if running inside a Python script or interactive console, False otherwise
441445 self ._in_py = False
442446
443- self .statement_parser = StatementParser (allow_redirection = allow_redirection ,
444- terminators = terminators ,
447+ self .statement_parser = StatementParser (terminators = terminators ,
445448 multiline_commands = multiline_commands ,
446449 shortcuts = shortcuts )
447450
@@ -616,16 +619,6 @@ def aliases(self) -> Dict[str, str]:
616619 """Read-only property to access the aliases stored in the StatementParser."""
617620 return self .statement_parser .aliases
618621
619- @property
620- def allow_redirection (self ) -> bool :
621- """Getter for the allow_redirection property that determines whether or not redirection of stdout is allowed."""
622- return self .statement_parser .allow_redirection
623-
624- @allow_redirection .setter
625- def allow_redirection (self , value : bool ) -> None :
626- """Setter for the allow_redirection property that determines whether or not redirection of stdout is allowed."""
627- self .statement_parser .allow_redirection = value
628-
629622 def poutput (self , msg : Any , * , end : str = '\n ' ) -> None :
630623 """Print message to self.stdout and appends a newline by default
631624
@@ -831,61 +824,56 @@ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[Li
831824 # Return empty lists since this means the line is malformed.
832825 return [], []
833826
834- if self .allow_redirection :
827+ # We need to treat redirection characters (|, <, >) as word breaks when they are in unquoted strings.
828+ # Go through each token and further split them on these characters. Each run of redirect characters
829+ # is treated as a single token.
830+ raw_tokens = []
835831
836- # Since redirection is enabled, we need to treat redirection characters (|, <, >)
837- # as word breaks when they are in unquoted strings. Go through each token
838- # and further split them on these characters. Each run of redirect characters
839- # is treated as a single token.
840- raw_tokens = []
832+ for cur_initial_token in initial_tokens :
841833
842- for cur_initial_token in initial_tokens :
834+ # Save tokens up to 1 character in length or quoted tokens. No need to parse these.
835+ if len (cur_initial_token ) <= 1 or cur_initial_token [0 ] in constants .QUOTES :
836+ raw_tokens .append (cur_initial_token )
837+ continue
843838
844- # Save tokens up to 1 character in length or quoted tokens. No need to parse these.
845- if len (cur_initial_token ) <= 1 or cur_initial_token [0 ] in constants .QUOTES :
846- raw_tokens .append (cur_initial_token )
847- continue
839+ # Iterate over each character in this token
840+ cur_index = 0
841+ cur_char = cur_initial_token [cur_index ]
848842
849- # Iterate over each character in this token
850- cur_index = 0
851- cur_char = cur_initial_token [cur_index ]
843+ # Keep track of the token we are building
844+ cur_raw_token = ''
852845
853- # Keep track of the token we are building
854- cur_raw_token = ''
846+ while True :
847+ if cur_char not in constants . REDIRECTION_CHARS :
855848
856- while True :
857- if cur_char not in constants .REDIRECTION_CHARS :
858-
859- # Keep appending to cur_raw_token until we hit a redirect char
860- while cur_char not in constants .REDIRECTION_CHARS :
861- cur_raw_token += cur_char
862- cur_index += 1
863- if cur_index < len (cur_initial_token ):
864- cur_char = cur_initial_token [cur_index ]
865- else :
866- break
849+ # Keep appending to cur_raw_token until we hit a redirect char
850+ while cur_char not in constants .REDIRECTION_CHARS :
851+ cur_raw_token += cur_char
852+ cur_index += 1
853+ if cur_index < len (cur_initial_token ):
854+ cur_char = cur_initial_token [cur_index ]
855+ else :
856+ break
867857
868- else :
869- redirect_char = cur_char
870-
871- # Keep appending to cur_raw_token until we hit something other than redirect_char
872- while cur_char == redirect_char :
873- cur_raw_token += cur_char
874- cur_index += 1
875- if cur_index < len (cur_initial_token ):
876- cur_char = cur_initial_token [cur_index ]
877- else :
878- break
858+ else :
859+ redirect_char = cur_char
860+
861+ # Keep appending to cur_raw_token until we hit something other than redirect_char
862+ while cur_char == redirect_char :
863+ cur_raw_token += cur_char
864+ cur_index += 1
865+ if cur_index < len (cur_initial_token ):
866+ cur_char = cur_initial_token [cur_index ]
867+ else :
868+ break
879869
880- # Save the current token
881- raw_tokens .append (cur_raw_token )
882- cur_raw_token = ''
870+ # Save the current token
871+ raw_tokens .append (cur_raw_token )
872+ cur_raw_token = ''
883873
884- # Check if we've viewed all characters
885- if cur_index >= len (cur_initial_token ):
886- break
887- else :
888- raw_tokens = initial_tokens
874+ # Check if we've viewed all characters
875+ if cur_index >= len (cur_initial_token ):
876+ break
889877
890878 # Save the unquoted tokens
891879 tokens = [utils .strip_quotes (cur_token ) for cur_token in raw_tokens ]
@@ -1228,72 +1216,70 @@ def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, com
12281216 this will be called if we aren't completing for redirection
12291217 :return: a list of possible tab completions
12301218 """
1231- if self .allow_redirection :
1232-
1233- # Get all tokens through the one being completed. We want the raw tokens
1234- # so we can tell if redirection strings are quoted and ignore them.
1235- _ , raw_tokens = self .tokens_for_completion (line , begidx , endidx )
1236- if not raw_tokens :
1237- return []
1219+ # Get all tokens through the one being completed. We want the raw tokens
1220+ # so we can tell if redirection strings are quoted and ignore them.
1221+ _ , raw_tokens = self .tokens_for_completion (line , begidx , endidx )
1222+ if not raw_tokens :
1223+ return []
12381224
1239- # Must at least have the command
1240- if len (raw_tokens ) > 1 :
1225+ # Must at least have the command
1226+ if len (raw_tokens ) > 1 :
12411227
1242- # True when command line contains any redirection tokens
1243- has_redirection = False
1228+ # True when command line contains any redirection tokens
1229+ has_redirection = False
12441230
1245- # Keep track of state while examining tokens
1246- in_pipe = False
1247- in_file_redir = False
1248- do_shell_completion = False
1249- do_path_completion = False
1250- prior_token = None
1231+ # Keep track of state while examining tokens
1232+ in_pipe = False
1233+ in_file_redir = False
1234+ do_shell_completion = False
1235+ do_path_completion = False
1236+ prior_token = None
12511237
1252- for cur_token in raw_tokens :
1253- # Process redirection tokens
1254- if cur_token in constants .REDIRECTION_TOKENS :
1255- has_redirection = True
1238+ for cur_token in raw_tokens :
1239+ # Process redirection tokens
1240+ if cur_token in constants .REDIRECTION_TOKENS :
1241+ has_redirection = True
12561242
1257- # Check if we are at a pipe
1258- if cur_token == constants .REDIRECTION_PIPE :
1259- # Do not complete bad syntax (e.g cmd | |)
1260- if prior_token == constants .REDIRECTION_PIPE :
1261- return []
1243+ # Check if we are at a pipe
1244+ if cur_token == constants .REDIRECTION_PIPE :
1245+ # Do not complete bad syntax (e.g cmd | |)
1246+ if prior_token == constants .REDIRECTION_PIPE :
1247+ return []
12621248
1263- in_pipe = True
1264- in_file_redir = False
1249+ in_pipe = True
1250+ in_file_redir = False
12651251
1266- # Otherwise this is a file redirection token
1267- else :
1268- if prior_token in constants .REDIRECTION_TOKENS or in_file_redir :
1269- # Do not complete bad syntax (e.g cmd | >) (e.g cmd > blah >)
1270- return []
1252+ # Otherwise this is a file redirection token
1253+ else :
1254+ if prior_token in constants .REDIRECTION_TOKENS or in_file_redir :
1255+ # Do not complete bad syntax (e.g cmd | >) (e.g cmd > blah >)
1256+ return []
12711257
1272- in_pipe = False
1273- in_file_redir = True
1258+ in_pipe = False
1259+ in_file_redir = True
12741260
1275- # Not a redirection token
1276- else :
1277- do_shell_completion = False
1278- do_path_completion = False
1261+ # Not a redirection token
1262+ else :
1263+ do_shell_completion = False
1264+ do_path_completion = False
12791265
1280- if prior_token == constants .REDIRECTION_PIPE :
1281- do_shell_completion = True
1282- elif in_pipe or prior_token in (constants .REDIRECTION_OUTPUT , constants .REDIRECTION_APPEND ):
1283- do_path_completion = True
1266+ if prior_token == constants .REDIRECTION_PIPE :
1267+ do_shell_completion = True
1268+ elif in_pipe or prior_token in (constants .REDIRECTION_OUTPUT , constants .REDIRECTION_APPEND ):
1269+ do_path_completion = True
12841270
1285- prior_token = cur_token
1271+ prior_token = cur_token
12861272
1287- if do_shell_completion :
1288- return self .shell_cmd_complete (text , line , begidx , endidx )
1273+ if do_shell_completion :
1274+ return self .shell_cmd_complete (text , line , begidx , endidx )
12891275
1290- elif do_path_completion :
1291- return self .path_complete (text , line , begidx , endidx )
1276+ elif do_path_completion :
1277+ return self .path_complete (text , line , begidx , endidx )
12921278
1293- # If there were redirection strings anywhere on the command line, then we
1294- # are no longer tab completing for the current command
1295- elif has_redirection :
1296- return []
1279+ # If there were redirection strings anywhere on the command line, then we
1280+ # are no longer tab completing for the current command
1281+ elif has_redirection :
1282+ return []
12971283
12981284 # Call the command's completer function
12991285 return compfunc (text , line , begidx , endidx )
@@ -2313,12 +2299,10 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
23132299 readline_settings .completer = readline .get_completer ()
23142300 readline .set_completer (self .complete )
23152301
2316- # Break words on whitespace and quotes when tab completing
2317- completer_delims = " \t \n " + '' .join (constants .QUOTES )
2318-
2319- if self .allow_redirection :
2320- # If redirection is allowed, then break words on those characters too
2321- completer_delims += '' .join (constants .REDIRECTION_CHARS )
2302+ # Break words on whitespace, quotes, and redirectors when tab completing
2303+ completer_delims = " \t \n "
2304+ completer_delims += '' .join (constants .QUOTES )
2305+ completer_delims += '' .join (constants .REDIRECTION_CHARS )
23222306
23232307 readline_settings .delims = readline .get_completer_delims ()
23242308 readline .set_completer_delims (completer_delims )
0 commit comments