@@ -363,6 +363,55 @@ def _load_duration(self, duration):
363363 # always
364364 return 8
365365
366+ def _comma_to_regexp (self , text , expected_type ):
367+ """translates items separated by comma, to regular expression
368+ returns True|False, regexp|error
369+ """
370+ s_parts = text .replace (" " , "" ).split ("," )
371+ sp_regex = r'^('
372+ for p in s_parts :
373+ if expected_type == int :
374+ try :
375+ int (p )
376+ except :
377+ return False , QC .translate ("rules" , "Invalid text" )
378+ if p == "" :
379+ return False , QC .translate ("rules" , "Invalid text" )
380+
381+ sp_regex += '{0}|' .format (p )
382+ sp_regex = sp_regex .removesuffix ("|" )
383+ sp_regex += r')$'
384+ if not self ._is_valid_regex (sp_regex ):
385+ return False , QC .translate ("rules" , "regexp error (report it)" )
386+
387+ return True , sp_regex
388+
389+ def _regexp_to_comma (self , text , expected_type ):
390+ """translates a regular expression to a comma separated list
391+ from ^(1|2|3)$ to "1,2,3"
392+ """
393+ error = ""
394+ # match ^(1|2|3)$
395+ regexp_str = r'\^\(([\d|]+)\)\$'
396+ if expected_type == str :
397+ # match ^(www.a-b-c.org|fff.uk|ooo.tw)$
398+ regexp_str = r'\^\(([.\-\w|]+)\)\$'
399+ q = re .search (regexp_str , text )
400+ if not q :
401+ return None , error
402+ try :
403+ parts = q .group (1 ).split ("|" )
404+ for p in parts :
405+ # unlikely. The regexp should haven't match.
406+ if expected_type == int :
407+ int (p )
408+ return "," .join (parts ), ""
409+ except Exception as e :
410+ print ("_regexp_to_comma exception:" , e )
411+ error = "Error parsing regexp to comma: {0}" .format (e )
412+
413+ return None , error
414+
366415 def _is_regex (self , text ):
367416 charset = "\\ *{[|^?$"
368417 for c in charset :
@@ -500,7 +549,12 @@ def _load_rule_operator(self, operator):
500549 if operator .operand == Config .OPERAND_PROTOCOL :
501550 self .protoCheck .setChecked (True )
502551 self .protoCombo .setEnabled (True )
503- self .protoCombo .setCurrentText (operator .data .upper ())
552+ prots , err = self ._regexp_to_comma (operator .data , str )
553+ if err != "" :
554+ self ._set_status_error (err )
555+ if prots is None :
556+ prots = operator .data
557+ self .protoCombo .setCurrentText (prots .upper ())
504558
505559 if operator .operand == Config .OPERAND_PROCESS_PATH :
506560 self .procCheck .setChecked (True )
@@ -529,17 +583,32 @@ def _load_rule_operator(self, operator):
529583 if operator .operand == Config .OPERAND_IFACE_OUT :
530584 self .ifaceCheck .setChecked (True )
531585 self .ifaceCombo .setEnabled (True )
532- self .ifaceCombo .setCurrentText (operator .data )
586+ ifaces , err = self ._regexp_to_comma (operator .data , str )
587+ if err != "" :
588+ self ._set_status_error (err )
589+ if ifaces is None :
590+ ifaces = operator .data
591+ self .ifaceCombo .setCurrentText (ifaces )
533592
534593 if operator .operand == Config .OPERAND_SOURCE_PORT :
535594 self .srcPortCheck .setChecked (True )
536595 self .srcPortLine .setEnabled (True )
537- self .srcPortLine .setText (operator .data )
596+ ports , err = self ._regexp_to_comma (operator .data , int )
597+ if err != "" :
598+ self ._set_status_error (err )
599+ if ports is None :
600+ ports = operator .data
601+ self .srcPortLine .setText (ports )
538602
539603 if operator .operand == Config .OPERAND_DEST_PORT :
540604 self .dstPortCheck .setChecked (True )
541605 self .dstPortLine .setEnabled (True )
542- self .dstPortLine .setText (operator .data )
606+ ports , err = self ._regexp_to_comma (operator .data , int )
607+ if err != "" :
608+ self ._set_status_error (err )
609+ if ports is None :
610+ ports = operator .data
611+ self .dstPortLine .setText (ports )
543612
544613 if operator .operand == Config .OPERAND_SOURCE_IP or operator .operand == Config .OPERAND_SOURCE_NETWORK :
545614 self .srcIPCheck .setChecked (True )
@@ -549,7 +618,12 @@ def _load_rule_operator(self, operator):
549618 elif operator .data == self .MULTICAST_RANGE :
550619 self .srcIPCombo .setCurrentText (self .MULTICAST_LABEL )
551620 else :
552- self .srcIPCombo .setCurrentText (operator .data )
621+ ips , err = self ._regexp_to_comma (operator .data , str )
622+ if err != "" :
623+ self ._set_status_error (err )
624+ if ips is None :
625+ ips = operator .data
626+ self .srcIPCombo .setCurrentText (ips )
553627
554628 if operator .operand == Config .OPERAND_DEST_IP or operator .operand == Config .OPERAND_DEST_NETWORK :
555629 self .dstIPCheck .setChecked (True )
@@ -559,12 +633,22 @@ def _load_rule_operator(self, operator):
559633 elif operator .data == self .MULTICAST_RANGE :
560634 self .dstIPCombo .setCurrentText (self .MULTICAST_LABEL )
561635 else :
562- self .dstIPCombo .setCurrentText (operator .data )
636+ ips , err = self ._regexp_to_comma (operator .data , str )
637+ if err != "" :
638+ self ._set_status_error (err )
639+ if ips is None :
640+ ips = operator .data
641+ self .dstIPCombo .setCurrentText (ips )
563642
564643 if operator .operand == Config .OPERAND_DEST_HOST :
565644 self .dstHostCheck .setChecked (True )
566645 self .dstHostLine .setEnabled (True )
567- self .dstHostLine .setText (operator .data )
646+ hosts , err = self ._regexp_to_comma (operator .data , str )
647+ if err != "" :
648+ self ._set_status_error (err )
649+ if hosts is None :
650+ hosts = operator .data
651+ self .dstHostLine .setText (hosts )
568652
569653 if operator .operand == Config .OPERAND_LIST_DOMAINS :
570654 self .dstListsCheck .setChecked (True )
@@ -715,6 +799,14 @@ def _save_rule(self):
715799 if self ._is_valid_regex (self .protoCombo .currentText ()) == False :
716800 return False , QC .translate ("rules" , "Protocol regexp error" )
717801
802+ elif "," in self .protoCombo .currentText ():
803+ ok , result = self ._comma_to_regexp (self .protoCombo .currentText ().lower (), str )
804+ if ok :
805+ rule_data [len (rule_data )- 1 ]['type' ] = Config .RULE_TYPE_REGEXP
806+ rule_data [len (rule_data )- 1 ]['data' ] = result
807+ else :
808+ return False , result
809+
718810 if self .procCheck .isChecked ():
719811 if self .procLine .text () == "" :
720812 return False , QC .translate ("rules" , "process path can not be empty" )
@@ -769,6 +861,14 @@ def _save_rule(self):
769861 if self ._is_valid_regex (self .ifaceCombo .currentText ()) == False :
770862 return False , QC .translate ("rules" , "Network interface regexp error" )
771863
864+ elif "," in self .ifaceCombo .currentText ():
865+ ok , result = self ._comma_to_regexp (self .ifaceCombo .currentText (), str )
866+ if ok :
867+ rule_data [len (rule_data )- 1 ]['type' ] = Config .RULE_TYPE_REGEXP
868+ rule_data [len (rule_data )- 1 ]['data' ] = result
869+ else :
870+ return False , result
871+
772872 if self .srcPortCheck .isChecked ():
773873 if self .srcPortLine .text () == "" :
774874 return False , QC .translate ("rules" , "Source port can not be empty" )
@@ -787,6 +887,14 @@ def _save_rule(self):
787887 if self ._is_valid_regex (self .srcPortLine .text ()) == False :
788888 return False , QC .translate ("rules" , "Source port regexp error" )
789889
890+ elif "," in self .srcPortLine .text ():
891+ ok , result = self ._comma_to_regexp (self .srcPortLine .text (), int )
892+ if ok :
893+ rule_data [len (rule_data )- 1 ]['type' ] = Config .RULE_TYPE_REGEXP
894+ rule_data [len (rule_data )- 1 ]['data' ] = result
895+ else :
896+ return False , result
897+
790898 if self .dstPortCheck .isChecked ():
791899 if self .dstPortLine .text () == "" :
792900 return False , QC .translate ("rules" , "Dest port can not be empty" )
@@ -805,6 +913,14 @@ def _save_rule(self):
805913 if self ._is_valid_regex (self .dstPortLine .text ()) == False :
806914 return False , QC .translate ("rules" , "Dst port regexp error" )
807915
916+ elif "," in self .dstPortLine .text ():
917+ ok , result = self ._comma_to_regexp (self .dstPortLine .text (), int )
918+ if ok :
919+ rule_data [len (rule_data )- 1 ]['type' ] = Config .RULE_TYPE_REGEXP
920+ rule_data [len (rule_data )- 1 ]['data' ] = result
921+ else :
922+ return False , result
923+
808924 if self .dstHostCheck .isChecked ():
809925 if self .dstHostLine .text () == "" :
810926 return False , QC .translate ("rules" , "Dest host can not be empty" )
@@ -823,6 +939,14 @@ def _save_rule(self):
823939 if self ._is_valid_regex (self .dstHostLine .text ()) == False :
824940 return False , QC .translate ("rules" , "Dst host regexp error" )
825941
942+ elif "," in self .dstHostLine .text ():
943+ ok , result = self ._comma_to_regexp (self .dstHostLine .text (), str )
944+ if ok :
945+ rule_data [len (rule_data )- 1 ]['type' ] = Config .RULE_TYPE_REGEXP
946+ rule_data [len (rule_data )- 1 ]['data' ] = result
947+ else :
948+ return False , result
949+
826950 if self .srcIPCheck .isChecked ():
827951 if self .srcIPCombo .currentText () == "" :
828952 return False , QC .translate ("rules" , "Source IP/Network can not be empty" )
@@ -853,6 +977,15 @@ def _save_rule(self):
853977 if self ._is_valid_regex (self .srcIPCombo .currentText ()) == False :
854978 return False , QC .translate ("rules" , "Source IP regexp error" )
855979
980+ elif "," in srcIPtext :
981+ ok , result = self ._comma_to_regexp (srcIPtext , str )
982+ if ok :
983+ self .rule .operator .operand = Config .OPERAND_SOURCE_IP
984+ self .rule .operator .type = Config .RULE_TYPE_REGEXP
985+ srcIPtext = result
986+ else :
987+ return False , result
988+
856989 rule_data .append (
857990 {
858991 'type' : self .rule .operator .type ,
@@ -895,6 +1028,14 @@ def _save_rule(self):
8951028 self .rule .operator .type = Config .RULE_TYPE_REGEXP
8961029 if self ._is_valid_regex (self .dstIPCombo .currentText ()) == False :
8971030 return False , QC .translate ("rules" , "Dst IP regexp error" )
1031+ elif "," in dstIPtext :
1032+ ok , result = self ._comma_to_regexp (dstIPtext , str )
1033+ if ok :
1034+ self .rule .operator .operand = Config .OPERAND_DEST_IP
1035+ self .rule .operator .type = Config .RULE_TYPE_REGEXP
1036+ dstIPtext = result
1037+ else :
1038+ return False , result
8981039
8991040 rule_data .append (
9001041 {
0 commit comments