groff
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[groff] [PATCH] Avoid Perl's unsafe "<>" operator


From: Colin Watson
Subject: [groff] [PATCH] Avoid Perl's unsafe "<>" operator
Date: Thu, 24 Jan 2019 14:34:35 +0000
User-agent: NeoMutt/20170113 (1.7.2)

The "<>" operator is implemented using the two-argument form of "open",
which interprets magic such as pipe characters, allowing execution of
arbitrary commands which is unlikely to be expected.  Perl >= 5.22 has a
"<<>>" operator which avoids this, but also forbids the use of "-" to
mean the standard input, which is a facility that the affected groff
programs document.

ARGV::readonly would probably also fix this, but I fundamentally dislike
the approach of escaping data in preparation for a language facility to
unescape it, especially when the required escaping is as non-obvious as
it is here.  (For the same reason, I prefer to use subprocess invocation
facilities that allow passing the argument list as a list rather than as
a string to be interpreted by the shell.)  So I've abandoned this
dubious convenience and changed the affected programs to iterate over
command-line arguments manually using the three-argument form of open.

This change involves an extra level of indentation, so it's a little
awkward to review.  It consists of changing this form:

  while (<>) {  # or foreach, which is similar but less efficient
    ...
  }

... into this:

  unshift @ARGV, '-' unless @ARGV;
  foreach my $filename (@ARGV) {
    my $input;
    if ($filename eq '-') {
      $input = \*STDIN;
    } elsif (not open $input, '<', $filename) {
      warn $!;
      next;
    }
    while (<$input>) {
      ...
    }
  }

Local variations: glilypond doesn't need the initial unshift since
that's already handled in contrib/glilypond/args.pl; gropdf declares
$input in a slightly different way since it's also used in the LoadAhead
function.

Fixes: https://bugs.debian.org/920269
---
 contrib/glilypond/glilypond.pl | 128 +++++++++++-----------
 contrib/gperl/gperl.pl         | 188 +++++++++++++++++----------------
 contrib/gpinyin/gpinyin.pl     |  88 ++++++++-------
 src/devices/gropdf/gropdf.pl   |  99 +++++++++--------
 tmac/hyphenex.pl               |  86 ++++++++-------
 5 files changed, 318 insertions(+), 271 deletions(-)

diff --git a/contrib/glilypond/glilypond.pl b/contrib/glilypond/glilypond.pl
index 868801b2..f2a76158 100755
--- a/contrib/glilypond/glilypond.pl
+++ b/contrib/glilypond/glilypond.pl
@@ -565,73 +565,81 @@ our $Read =
     ); # end definition %lilypond_args
 
 
- LILYPOND: foreach (<>) {
-    chomp;
-    my $line = $_;
+ LILYPOND: foreach my $filename (@ARGV) {
+    my $input;
+    if ($filename eq '-') {
+      $input = \*STDIN;
+    } elsif (not open $input, '<', $filename) {
+      warn $!;
+      next;
+    }
+    while (<$input>) {
+      chomp;
+      my $line = $_;
 
 
-    # now the lines with '.lilypond ...'
+      # now the lines with '.lilypond ...'
 
-    if ( /
-          ^
-          [.']
-          \s*
-          lilypond
-          (
-            .*
-          )
-          $
-        /x ) { # .lilypond ...
-      my $args = $1;
-      $args =~ s/
-                 ^
-                 \s*
-               //x;
-      $args =~ s/
-                 \s*
-                 $
-               //x;
-      $args =~ s/
-                 ^
-                 (
-                   \S*
-                 )
-                 \s*
-               //x;
-      my $arg1 = $1; # 'start', 'end' or 'include'
-      $args =~ s/["'`]//g;
-      my $arg2 = $args; # file argument for '.lilypond include'
-
-      if ( exists $lilypond_args{$arg1} ) {
-       $lilypond_args{$arg1}->($arg2);
-       next;
-      } else {
-       # not a suitable argument of '.lilypond'
-       $stderr->print( "Unknown command: '$arg1' '$arg2':  '$line'" );
-      }
-
-      next LILYPOND;
-    } # end if for .lilypond
+      if ( /
+            ^
+            [.']
+            \s*
+            lilypond
+            (
+              .*
+            )
+            $
+          /x ) { # .lilypond ...
+       my $args = $1;
+       $args =~ s/
+                   ^
+                   \s*
+                 //x;
+       $args =~ s/
+                   \s*
+                   $
+                 //x;
+       $args =~ s/
+                   ^
+                   (
+                     \S*
+                   )
+                   \s*
+                 //x;
+       my $arg1 = $1; # 'start', 'end' or 'include'
+       $args =~ s/["'`]//g;
+       my $arg2 = $args; # file argument for '.lilypond include'
+
+       if ( exists $lilypond_args{$arg1} ) {
+         $lilypond_args{$arg1}->($arg2);
+         next;
+       } else {
+         # not a suitable argument of '.lilypond'
+         $stderr->print( "Unknown command: '$arg1' '$arg2':  '$line'" );
+       }
 
+       next LILYPOND;
+      } # end if for .lilypond
 
-    if ( $lilypond_mode ) { # do lilypond-mode
-      # see '.lilypond start'
-      $ly->print( $line );
-      next LILYPOND;
-    } # do lilypond-mode
 
-    # unknown line without lilypond
-    unless ( /
-              ^
-              [.']
-              \s*
-              lilypond
-            /x ) { # not a '.lilypond' line
-      $out->print($line);
-      next LILYPOND;
-    }
+      if ( $lilypond_mode ) { # do lilypond-mode
+       # see '.lilypond start'
+       $ly->print( $line );
+       next LILYPOND;
+      } # do lilypond-mode
 
-  } # end foreach <>
+      # unknown line without lilypond
+      unless ( /
+                ^
+                [.']
+                \s*
+                lilypond
+              /x ) { # not a '.lilypond' line
+       $out->print($line);
+       next LILYPOND;
+      }
+    } # end while <$input>
+  } # end foreach $filename
 } # end Read
 
 
diff --git a/contrib/gperl/gperl.pl b/contrib/gperl/gperl.pl
index fdb93fff..6eb2f13b 100755
--- a/contrib/gperl/gperl.pl
+++ b/contrib/gperl/gperl.pl
@@ -132,114 +132,124 @@ my $out_file;
 
 my $perl_mode = 0;
 
-foreach (<>) {
-  chomp;
-  s/\s+$//;
-  my $line = $_;
-  my $is_dot_Perl = $line =~ /^[.']\s*Perl(|\s+.*)$/;
-
-  unless ( $is_dot_Perl ) {    # not a '.Perl' line
-    if ( $perl_mode ) {                # is running in Perl mode
-      print OUT $line;
-    } else {                   # normal line, not Perl-related
-      print $line;
-    }
+unshift @ARGV, '-' unless @ARGV;
+foreach my $filename (@ARGV) {
+  my $input;
+  if ($filename eq '-') {
+    $input = \*STDIN;
+  } elsif (not open $input, '<', $filename) {
+    warn $!;
     next;
   }
-
-
-  ##########
-  # now the line is a '.Perl' line
-
-  my $args = $line;
-  $args =~ s/\s+$//;   # remove final spaces
-  $args =~ s/^[.']\s*Perl\s*//;        # omit .Perl part, leave the arguments
-
-  my @args = split /\s+/, $args;
-
-  ##########
-  # start Perl mode
-  if ( @args == 0 || @args == 1 && $args[0] eq 'start' ) {
-    # For '.Perl' no args or first arg 'start' means opening 'Perl' mode.
-    # Everything else means an ending command.
-    if ( $perl_mode ) {
-      # '.Perl' was started twice, ignore
-      print STDERR q('.Perl' starter was run several times);
-      next;
-    } else {   # new Perl start
-      $perl_mode = 1;
-      open OUT, '>', $out_file;
+  while (<$input>) {
+    chomp;
+    s/\s+$//;
+    my $line = $_;
+    my $is_dot_Perl = $line =~ /^[.']\s*Perl(|\s+.*)$/;
+
+    unless ( $is_dot_Perl ) {  # not a '.Perl' line
+      if ( $perl_mode ) {              # is running in Perl mode
+        print OUT $line;
+      } else {                 # normal line, not Perl-related
+        print $line;
+      }
       next;
     }
-  }
 
-  ##########
-  # now the line must be a Perl ending line (stop)
 
-  unless ( $perl_mode ) {
-    print STDERR 'gperl: there was a Perl ending without being in ' .
-      'Perl mode:';
-    print STDERR '    ' . $line;
-    next;
-  }
+    ##########
+    # now the line is a '.Perl' line
+
+    my $args = $line;
+    $args =~ s/\s+$//; # remove final spaces
+    $args =~ s/^[.']\s*Perl\s*//;      # omit .Perl part, leave the arguments
+
+    my @args = split /\s+/, $args;
+
+    ##########
+    # start Perl mode
+    if ( @args == 0 || @args == 1 && $args[0] eq 'start' ) {
+      # For '.Perl' no args or first arg 'start' means opening 'Perl' mode.
+      # Everything else means an ending command.
+      if ( $perl_mode ) {
+        # '.Perl' was started twice, ignore
+        print STDERR q('.Perl' starter was run several times);
+        next;
+      } else { # new Perl start
+        $perl_mode = 1;
+        open OUT, '>', $out_file;
+        next;
+      }
+    }
 
-  $perl_mode = 0;      # 'Perl' stop calling is correct
-  close OUT;           # close the storing of 'Perl' commands
+    ##########
+    # now the line must be a Perl ending line (stop)
 
-  ##########
-  # run this 'Perl' part, later on about storage of the result
-  # array stores prints with \n
-  my @print_res = `perl $out_file`;
+    unless ( $perl_mode ) {
+      print STDERR 'gperl: there was a Perl ending without being in ' .
+        'Perl mode:';
+      print STDERR '    ' . $line;
+      next;
+    }
 
-  # remove 'stop' arg if exists
-  shift @args if ( $args[0] eq 'stop' );
+    $perl_mode = 0;    # 'Perl' stop calling is correct
+    close OUT;         # close the storing of 'Perl' commands
 
-  if ( @args == 0 ) {
-    # no args for saving, so @print_res doesn't matter
-    next;
-  }
+    ##########
+    # run this 'Perl' part, later on about storage of the result
+    # array stores prints with \n
+    my @print_res = `perl $out_file`;
 
-  my @var_names = ();
-  my @mode_names = ();
+    # remove 'stop' arg if exists
+    shift @args if ( $args[0] eq 'stop' );
 
-  my $mode = '.ds';
-  for ( @args ) {
-    if ( /^\.?ds$/ ) {
-      $mode = '.ds';
+    if ( @args == 0 ) {
+      # no args for saving, so @print_res doesn't matter
       next;
     }
-    if ( /^\.?nr$/ ) {
-      $mode = '.nr';
-      next;
+
+    my @var_names = ();
+    my @mode_names = ();
+
+    my $mode = '.ds';
+    for ( @args ) {
+      if ( /^\.?ds$/ ) {
+        $mode = '.ds';
+        next;
+      }
+      if ( /^\.?nr$/ ) {
+        $mode = '.nr';
+        next;
+      }
+      push @mode_names, $mode;
+      push @var_names, $_;
     }
-    push @mode_names, $mode;
-    push @var_names, $_;
-  }
 
-  my $n_res = @print_res;
-  my $n_vars = @var_names;
+    my $n_res = @print_res;
+    my $n_vars = @var_names;
 
-  if ( $n_vars < $n_res ) {
-    print STDERR 'gperl: not enough variables for Perl part: ' .
-      $n_vars . ' variables for ' . $n_res . ' output lines.';
-  } elsif ( $n_vars > $n_res ) {
-    print STDERR 'gperl: too many variablenames for Perl part: ' .
-      $n_vars . ' variables for ' . $n_res . ' output lines.';
-  }
-  if ( $n_vars < $n_res ) {
-    print STDERR 'gperl: not enough variables for Perl part: ' .
-      $n_vars . ' variables for ' . $n_res . ' output lines.';
-  }
+    if ( $n_vars < $n_res ) {
+      print STDERR 'gperl: not enough variables for Perl part: ' .
+        $n_vars . ' variables for ' . $n_res . ' output lines.';
+    } elsif ( $n_vars > $n_res ) {
+      print STDERR 'gperl: too many variablenames for Perl part: ' .
+        $n_vars . ' variables for ' . $n_res . ' output lines.';
+    }
+    if ( $n_vars < $n_res ) {
+      print STDERR 'gperl: not enough variables for Perl part: ' .
+        $n_vars . ' variables for ' . $n_res . ' output lines.';
+    }
 
-  my $n_min = $n_res;
-  $n_min = $n_vars if ( $n_vars < $n_res );
-  exit unless ( $n_min );
-  $n_min -= 1; # for starting with 0
+    my $n_min = $n_res;
+    $n_min = $n_vars if ( $n_vars < $n_res );
+    exit unless ( $n_min );
+    $n_min -= 1; # for starting with 0
 
-  for my $i ( 0..$n_min ) {
-    my $value = $print_res[$i];
-    chomp $value;
-    print $mode_names[$i] . ' ' . $var_names[$i] . ' ' . $value;
+    for my $i ( 0..$n_min ) {
+      my $value = $print_res[$i];
+      chomp $value;
+      print $mode_names[$i] . ' ' . $var_names[$i] . ' ' . $value;
+    }
   }
 }
 
diff --git a/contrib/gpinyin/gpinyin.pl b/contrib/gpinyin/gpinyin.pl
index e4bb5a31..57414f33 100755
--- a/contrib/gpinyin/gpinyin.pl
+++ b/contrib/gpinyin/gpinyin.pl
@@ -126,53 +126,63 @@ my @output_t =    # troff
    '.el \\{\\',
   );
 
-foreach (<>) { # get line from input
-  chomp;
-  s/\s+$//;            # remove final spaces
-# &err('gpinyin: ' . $_);
-
-  my $line = $_;       # with starting blanks
-
-  # .pinyin start or begin line
-  if ( $line =~ /^[.']\s*pinyin\s+(start|begin)$/ ) {
-    if ( $pinyin_mode ) {
-      # '.pinyin' was started twice, ignore
-      &err( q['.pinyin' starter was run several times] );
-    } else {   # new pinyin start
-      $pinyin_mode = 1;
-    }
+unshift @ARGV, '-' unless @ARGV;
+foreach my $filename (@ARGV) {
+  my $input;
+  if ($filename eq '-') {
+    $input = \*STDIN;
+  } elsif (not open $input, '<', $filename) {
+    warn $!;
     next;
   }
+  while (<$input>) {
+    chomp;
+    s/\s+$//;          # remove final spaces
+#   &err('gpinyin: ' . $_);
+
+    my $line = $_;     # with starting blanks
+
+    # .pinyin start or begin line
+    if ( $line =~ /^[.']\s*pinyin\s+(start|begin)$/ ) {
+      if ( $pinyin_mode ) {
+        # '.pinyin' was started twice, ignore
+        &err( q['.pinyin' starter was run several times] );
+      } else { # new pinyin start
+        $pinyin_mode = 1;
+      }
+      next;
+    }
 
-  # .pinyin stop or end line
-  if ( $line =~ /^[.']\s*pinyin\s+(stop|end)$/ ) {
-    if ( $pinyin_mode ) {              # normal stop
-      $pinyin_mode = 0;
-      &finish_pinyin_mode( address@hidden, address@hidden );
-    } else {   # ignore
-      &err( 'gpinyin: there was a .pinyin stop without ' .
-       'being in pinyin mode' );
+    # .pinyin stop or end line
+    if ( $line =~ /^[.']\s*pinyin\s+(stop|end)$/ ) {
+      if ( $pinyin_mode ) {            # normal stop
+        $pinyin_mode = 0;
+        &finish_pinyin_mode( address@hidden, address@hidden );
+      } else { # ignore
+        &err( 'gpinyin: there was a .pinyin stop without ' .
+          'being in pinyin mode' );
+      }
+      next;
     }
-    next;
-  }
 
-  # now not a .pinyin line
+    # now not a .pinyin line
 
 
-  if ( $pinyin_mode ) {        # within Pinyin
-    my $starting_blanks = '';
-    $starting_blanks = $1 if ( s/^(s+)// );    # handle starting spaces
+    if ( $pinyin_mode ) {      # within Pinyin
+      my $starting_blanks = '';
+      $starting_blanks = $1 if ( s/^(s+)// );  # handle starting spaces
 
-    my %outline = &handle_line($starting_blanks, $line);
-#&err('gpinyin outline n: ' . $outline{'n'} );
-#&err('gpinyin outline t: ' . $outline{'t'} );
-    push @output_n, $outline{'n'};
-    push @output_t, $outline{'t'};
-  } else {     # normal roff line, not within Pinyin
-    print $line;
-  }
-  next;
-}      # end of input line
+      my %outline = &handle_line($starting_blanks, $line);
+#     &err('gpinyin outline n: ' . $outline{'n'} );
+#     &err('gpinyin outline t: ' . $outline{'t'} );
+      push @output_n, $outline{'n'};
+      push @output_t, $outline{'t'};
+    } else {   # normal roff line, not within Pinyin
+      print $line;
+    }
+    next;
+  }    # end of input line
+}
 
 
 ########################################################################
diff --git a/src/devices/gropdf/gropdf.pl b/src/devices/gropdf/gropdf.pl
index 2ec52d06..b08c4b18 100644
--- a/src/devices/gropdf/gropdf.pl
+++ b/src/devices/gropdf/gropdf.pl
@@ -61,6 +61,7 @@ my @obj;      # Array of PDF objects
 my $objct=0;   # Count of Objects
 my $fct=0;     # Output count
 my %fnt;       # Used fonts
+our $input;    # Current input filehandle
 my $lct=0;     # Input Line Count
 my $src_name='';
 my %env;       # Current environment
@@ -288,56 +289,64 @@ my %info=('Creator' => "(groff version 
$cfg{GROFF_VERSION})",
                                'ModDate' => "($dt)",
                                'CreationDate' => "($dt)");
 
-while (<>)
-{
-    chomp;
-    s/\r$//;
-    $lct++;
+unshift @ARGV, '-' unless @ARGV;
+foreach my $filename (@ARGV) {
+    local $input;
+    if ($filename eq '-') {
+       $input = \*STDIN;
+    } elsif (not open $input, '<', $filename) {
+       warn $!;
+       next;
+    }
+    while (<$input>) {
+       chomp;
+       s/\r$//;
+       $lct++;
 
-    do         # The ahead buffer behaves like 'ungetc'
-    {{
-       if (scalar(@ahead))
-       {
-           $_=shift(@ahead);
-       }
+       do      # The ahead buffer behaves like 'ungetc'
+       {{
+           if (scalar(@ahead))
+           {
+               $_=shift(@ahead);
+           }
 
 
-       my $cmd=substr($_,0,1);
-       next if $cmd eq '#';    # just a comment
-       my $lin=substr($_,1);
+           my $cmd=substr($_,0,1);
+           next if $cmd eq '#';        # just a comment
+           my $lin=substr($_,1);
 
-       while ($cmd eq 'w')
-       {
-           $cmd=substr($lin,0,1);
-           $lin=substr($lin,1);
-           $w_flg=1 if $gotT;
-       }
+           while ($cmd eq 'w')
+           {
+               $cmd=substr($lin,0,1);
+               $lin=substr($lin,1);
+               $w_flg=1 if $gotT;
+           }
 
-       $lin=~s/^\s+//;
+           $lin=~s/^\s+//;
 #              $lin=~s/\s#.*?$//;      # remove comment
-       $stream.="\% $_\n" if $debug;
-
-       do_x($lin),next if ($cmd eq 'x');
-       next if $suppress;
-       do_p($lin),next if ($cmd eq 'p');
-       do_f($lin),next if ($cmd eq 'f');
-       do_s($lin),next if ($cmd eq 's');
-       do_m($lin),next if ($cmd eq 'm');
-       do_D($lin),next if ($cmd eq 'D');
-       do_V($lin),next if ($cmd eq 'V');
-       do_v($lin),next if ($cmd eq 'v');
-       do_t($lin),next if ($cmd eq 't');
-       do_u($lin),next if ($cmd eq 'u');
-       do_C($lin),next if ($cmd eq 'C');
-       do_c($lin),next if ($cmd eq 'c');
-       do_N($lin),next if ($cmd eq 'N');
-       do_h($lin),next if ($cmd eq 'h');
-       do_H($lin),next if ($cmd eq 'H');
-       do_n($lin),next if ($cmd eq 'n');
-
-       my $tmp=scalar(@ahead);
-    }} until scalar(@ahead) == 0;
-
+           $stream.="\% $_\n" if $debug;
+
+           do_x($lin),next if ($cmd eq 'x');
+           next if $suppress;
+           do_p($lin),next if ($cmd eq 'p');
+           do_f($lin),next if ($cmd eq 'f');
+           do_s($lin),next if ($cmd eq 's');
+           do_m($lin),next if ($cmd eq 'm');
+           do_D($lin),next if ($cmd eq 'D');
+           do_V($lin),next if ($cmd eq 'V');
+           do_v($lin),next if ($cmd eq 'v');
+           do_t($lin),next if ($cmd eq 't');
+           do_u($lin),next if ($cmd eq 'u');
+           do_C($lin),next if ($cmd eq 'C');
+           do_c($lin),next if ($cmd eq 'c');
+           do_N($lin),next if ($cmd eq 'N');
+           do_h($lin),next if ($cmd eq 'h');
+           do_H($lin),next if ($cmd eq 'H');
+           do_n($lin),next if ($cmd eq 'n');
+
+           my $tmp=scalar(@ahead);
+       }} until scalar(@ahead) == 0;
+    }
 }
 
 exit 0 if $lct==0;
@@ -3248,7 +3257,7 @@ sub  LoadAhead
 
     foreach my $j (1..$no)
     {
-       my $lin=<>;
+       my $lin=<$input>;
        chomp($lin);
        $lin=~s/\r$//;
        $lct++;
diff --git a/tmac/hyphenex.pl b/tmac/hyphenex.pl
index fba3e8d6..aee5845b 100644
--- a/tmac/hyphenex.pl
+++ b/tmac/hyphenex.pl
@@ -31,47 +31,57 @@ print "% for corrections and omissions.\n";
 print "\n";
 print "\\hyphenation{\n";
 
-while (<>) {
-  # retain only lines starting with \1 ... \6 or \tabalign
-  next if not (m/^\\[123456]/ || m/^\\tabalign/);
-  # remove final newline
-  chop;
-  # remove all TeX commands except \1 ... \6
-  s/\\[^123456\s{]+//g;
-  # remove all paired { ... }
-  1 while s/{(.*?)}/\1/g;
-  # skip lines which now have only whitespace before '&'
-  next if m/^\s*&/;
-  # remove comments
-  s/%.*//;
-  # remove trailing whitespace
-  s/\s*$//;
-  # remove trailing '*' (used as a marker in the document)
-  s/\*$//;
-  # split at whitespace
-  @field = split(' ');
-  if ($field[0] eq "\\1" || $field[0] eq "\\4") {
-    print "  $field[2]\n";
+unshift @ARGV, '-' unless @ARGV;
+foreach my $filename (@ARGV) {
+  my $input;
+  if ($filename eq '-') {
+    $input = \*STDIN;
+  } elsif (not open $input, '<', $filename) {
+    warn $!;
+    next;
   }
-  elsif ($field[0] eq "\\2" || $field[0] eq "\\5") {
-    print "  $field[2]\n";
-    # handle multiple suffixes separated by commata
-    @suffix_list = split(/,/, "$field[3]");
-    foreach $suffix (@suffix_list) {
-      print "  $field[2]$suffix\n";
+  while (<$input>) {
+    # retain only lines starting with \1 ... \6 or \tabalign
+    next if not (m/^\\[123456]/ || m/^\\tabalign/);
+    # remove final newline
+    chop;
+    # remove all TeX commands except \1 ... \6
+    s/\\[^123456\s{]+//g;
+    # remove all paired { ... }
+    1 while s/{(.*?)}/\1/g;
+    # skip lines which now have only whitespace before '&'
+    next if m/^\s*&/;
+    # remove comments
+    s/%.*//;
+    # remove trailing whitespace
+    s/\s*$//;
+    # remove trailing '*' (used as a marker in the document)
+    s/\*$//;
+    # split at whitespace
+    @field = split(' ');
+    if ($field[0] eq "\\1" || $field[0] eq "\\4") {
+      print "  $field[2]\n";
     }
-  }
-  elsif ($field[0] eq "\\3" || $field[0] eq "\\6") {
-    # handle multiple suffixes separated by commata
-    @suffix_list = split(/,/, "$field[3],$field[4]");
-    foreach $suffix (@suffix_list) {
-      print "  $field[2]$suffix\n";
+    elsif ($field[0] eq "\\2" || $field[0] eq "\\5") {
+      print "  $field[2]\n";
+      # handle multiple suffixes separated by commata
+      @suffix_list = split(/,/, "$field[3]");
+      foreach $suffix (@suffix_list) {
+        print "  $field[2]$suffix\n";
+      }
+    }
+    elsif ($field[0] eq "\\3" || $field[0] eq "\\6") {
+      # handle multiple suffixes separated by commata
+      @suffix_list = split(/,/, "$field[3],$field[4]");
+      foreach $suffix (@suffix_list) {
+        print "  $field[2]$suffix\n";
+      }
+    }
+    else {
+      # for '&', split at '&' with trailing whitespace
+      @field = split(/&\s*/);
+      print "  $field[1]\n";
     }
-  }
-  else {
-    # for '&', split at '&' with trailing whitespace
-    @field = split(/&\s*/);
-    print "  $field[1]\n";
   }
 }
 
-- 
2.20.1



reply via email to

[Prev in Thread] Current Thread [Next in Thread]