@@ -353,7 +353,8 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
353
353
commands to be run or, if -t is specified, transcript files to run.
354
354
This should be set to False if your application parses its own arguments.
355
355
: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.
357
358
:param multiline_commands: list of commands allowed to accept multi-line input
358
359
:param terminators: list of characters that terminate a command. These are mainly intended for terminating
359
360
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, *,
376
377
# Call super class constructor
377
378
super ().__init__ (completekey = completekey , stdin = stdin , stdout = stdout )
378
379
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.
380
383
self .default_to_shell = False # Attempt to run unrecognized commands as shell commands
381
384
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
382
386
383
- # Attributes which ARE dynamically settable at runtime
387
+ # Attributes which ARE dynamically settable via the set command at runtime
384
388
self .continuation_prompt = '> '
385
389
self .debug = False
386
390
self .echo = False
@@ -440,8 +444,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
440
444
# True if running inside a Python script or interactive console, False otherwise
441
445
self ._in_py = False
442
446
443
- self .statement_parser = StatementParser (allow_redirection = allow_redirection ,
444
- terminators = terminators ,
447
+ self .statement_parser = StatementParser (terminators = terminators ,
445
448
multiline_commands = multiline_commands ,
446
449
shortcuts = shortcuts )
447
450
@@ -616,16 +619,6 @@ def aliases(self) -> Dict[str, str]:
616
619
"""Read-only property to access the aliases stored in the StatementParser."""
617
620
return self .statement_parser .aliases
618
621
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
-
629
622
def poutput (self , msg : Any , * , end : str = '\n ' ) -> None :
630
623
"""Print message to self.stdout and appends a newline by default
631
624
@@ -831,61 +824,56 @@ def tokens_for_completion(self, line: str, begidx: int, endidx: int) -> Tuple[Li
831
824
# Return empty lists since this means the line is malformed.
832
825
return [], []
833
826
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 = []
835
831
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 :
841
833
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
843
838
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 ]
848
842
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 = ''
852
845
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 :
855
848
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
867
857
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
879
869
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 = ''
883
873
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
889
877
890
878
# Save the unquoted tokens
891
879
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
1228
1216
this will be called if we aren't completing for redirection
1229
1217
:return: a list of possible tab completions
1230
1218
"""
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 []
1238
1224
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 :
1241
1227
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
1244
1230
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
1251
1237
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
1256
1242
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 []
1262
1248
1263
- in_pipe = True
1264
- in_file_redir = False
1249
+ in_pipe = True
1250
+ in_file_redir = False
1265
1251
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 []
1271
1257
1272
- in_pipe = False
1273
- in_file_redir = True
1258
+ in_pipe = False
1259
+ in_file_redir = True
1274
1260
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
1279
1265
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
1284
1270
1285
- prior_token = cur_token
1271
+ prior_token = cur_token
1286
1272
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 )
1289
1275
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 )
1292
1278
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 []
1297
1283
1298
1284
# Call the command's completer function
1299
1285
return compfunc (text , line , begidx , endidx )
@@ -2313,12 +2299,10 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
2313
2299
readline_settings .completer = readline .get_completer ()
2314
2300
readline .set_completer (self .complete )
2315
2301
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 )
2322
2306
2323
2307
readline_settings .delims = readline .get_completer_delims ()
2324
2308
readline .set_completer_delims (completer_delims )
0 commit comments