1*b0d17251Schristos#! /usr/bin/env perl 2*b0d17251Schristos# Copyright 2018-2021 The OpenSSL Project Authors. All Rights Reserved. 3*b0d17251Schristos# 4*b0d17251Schristos# Licensed under the Apache License 2.0 (the "License"). You may not use 5*b0d17251Schristos# this file except in compliance with the License. You can obtain a copy 6*b0d17251Schristos# in the file LICENSE in the source distribution or at 7*b0d17251Schristos# https://www.openssl.org/source/license.html 8*b0d17251Schristos 9*b0d17251Schristospackage OpenSSL::Util; 10*b0d17251Schristos 11*b0d17251Schristosuse strict; 12*b0d17251Schristosuse warnings; 13*b0d17251Schristosuse Carp; 14*b0d17251Schristos 15*b0d17251Schristosuse Exporter; 16*b0d17251Schristosuse vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); 17*b0d17251Schristos$VERSION = "0.1"; 18*b0d17251Schristos@ISA = qw(Exporter); 19*b0d17251Schristos@EXPORT = qw(cmp_versions quotify1 quotify_l fixup_cmd_elements fixup_cmd 20*b0d17251Schristos dump_data); 21*b0d17251Schristos@EXPORT_OK = qw(); 22*b0d17251Schristos 23*b0d17251Schristos=head1 NAME 24*b0d17251Schristos 25*b0d17251SchristosOpenSSL::Util - small OpenSSL utilities 26*b0d17251Schristos 27*b0d17251Schristos=head1 SYNOPSIS 28*b0d17251Schristos 29*b0d17251Schristos use OpenSSL::Util; 30*b0d17251Schristos 31*b0d17251Schristos $versiondiff = cmp_versions('1.0.2k', '3.0.1'); 32*b0d17251Schristos # $versiondiff should be -1 33*b0d17251Schristos 34*b0d17251Schristos $versiondiff = cmp_versions('1.1.0', '1.0.2a'); 35*b0d17251Schristos # $versiondiff should be 1 36*b0d17251Schristos 37*b0d17251Schristos $versiondiff = cmp_versions('1.1.1', '1.1.1'); 38*b0d17251Schristos # $versiondiff should be 0 39*b0d17251Schristos 40*b0d17251Schristos=head1 DESCRIPTION 41*b0d17251Schristos 42*b0d17251Schristos=over 43*b0d17251Schristos 44*b0d17251Schristos=item B<cmp_versions "VERSION1", "VERSION2"> 45*b0d17251Schristos 46*b0d17251SchristosCompares VERSION1 with VERSION2, paying attention to OpenSSL versioning. 47*b0d17251Schristos 48*b0d17251SchristosReturns 1 if VERSION1 is greater than VERSION2, 0 if they are equal, and 49*b0d17251Schristos-1 if VERSION1 is less than VERSION2. 50*b0d17251Schristos 51*b0d17251Schristos=back 52*b0d17251Schristos 53*b0d17251Schristos=cut 54*b0d17251Schristos 55*b0d17251Schristos# Until we're rid of everything with the old version scheme, 56*b0d17251Schristos# we need to be able to handle older style x.y.zl versions. 57*b0d17251Schristos# In terms of comparison, the x.y.zl and the x.y.z schemes 58*b0d17251Schristos# are compatible... mostly because the latter starts at a 59*b0d17251Schristos# new major release with a new major number. 60*b0d17251Schristossub _ossl_versionsplit { 61*b0d17251Schristos my $textversion = shift; 62*b0d17251Schristos return $textversion if $textversion eq '*'; 63*b0d17251Schristos my ($major,$minor,$edit,$letter) = 64*b0d17251Schristos $textversion =~ /^(\d+)\.(\d+)\.(\d+)([a-z]{0,2})$/; 65*b0d17251Schristos 66*b0d17251Schristos return ($major,$minor,$edit,$letter); 67*b0d17251Schristos} 68*b0d17251Schristos 69*b0d17251Schristossub cmp_versions { 70*b0d17251Schristos my @a_split = _ossl_versionsplit(shift); 71*b0d17251Schristos my @b_split = _ossl_versionsplit(shift); 72*b0d17251Schristos my $verdict = 0; 73*b0d17251Schristos 74*b0d17251Schristos while (@a_split) { 75*b0d17251Schristos # The last part is a letter sequence (or a '*') 76*b0d17251Schristos if (scalar @a_split == 1) { 77*b0d17251Schristos $verdict = $a_split[0] cmp $b_split[0]; 78*b0d17251Schristos } else { 79*b0d17251Schristos $verdict = $a_split[0] <=> $b_split[0]; 80*b0d17251Schristos } 81*b0d17251Schristos shift @a_split; 82*b0d17251Schristos shift @b_split; 83*b0d17251Schristos last unless $verdict == 0; 84*b0d17251Schristos } 85*b0d17251Schristos 86*b0d17251Schristos return $verdict; 87*b0d17251Schristos} 88*b0d17251Schristos 89*b0d17251Schristos# It might be practical to quotify some strings and have them protected 90*b0d17251Schristos# from possible harm. These functions primarily quote things that might 91*b0d17251Schristos# be interpreted wrongly by a perl eval. 92*b0d17251Schristos 93*b0d17251Schristos=over 4 94*b0d17251Schristos 95*b0d17251Schristos=item quotify1 STRING 96*b0d17251Schristos 97*b0d17251SchristosThis adds quotes (") around the given string, and escapes any $, @, \, 98*b0d17251Schristos" and ' by prepending a \ to them. 99*b0d17251Schristos 100*b0d17251Schristos=back 101*b0d17251Schristos 102*b0d17251Schristos=cut 103*b0d17251Schristos 104*b0d17251Schristossub quotify1 { 105*b0d17251Schristos my $s = shift @_; 106*b0d17251Schristos $s =~ s/([\$\@\\"'])/\\$1/g; 107*b0d17251Schristos '"'.$s.'"'; 108*b0d17251Schristos} 109*b0d17251Schristos 110*b0d17251Schristos=over 4 111*b0d17251Schristos 112*b0d17251Schristos=item quotify_l LIST 113*b0d17251Schristos 114*b0d17251SchristosFor each defined element in LIST (i.e. elements that aren't undef), have 115*b0d17251Schristosit quotified with 'quotify1'. 116*b0d17251SchristosUndefined elements are ignored. 117*b0d17251Schristos 118*b0d17251Schristos=cut 119*b0d17251Schristos 120*b0d17251Schristossub quotify_l { 121*b0d17251Schristos map { 122*b0d17251Schristos if (!defined($_)) { 123*b0d17251Schristos (); 124*b0d17251Schristos } else { 125*b0d17251Schristos quotify1($_); 126*b0d17251Schristos } 127*b0d17251Schristos } @_; 128*b0d17251Schristos} 129*b0d17251Schristos 130*b0d17251Schristos=over 4 131*b0d17251Schristos 132*b0d17251Schristos=item fixup_cmd_elements LIST 133*b0d17251Schristos 134*b0d17251SchristosFixes up the command line elements given by LIST in a platform specific 135*b0d17251Schristosmanner. 136*b0d17251Schristos 137*b0d17251SchristosThe result of this function is a copy of LIST with strings where quotes and 138*b0d17251Schristosescapes have been injected as necessary depending on the content of each 139*b0d17251SchristosLIST string. 140*b0d17251Schristos 141*b0d17251SchristosThis can also be used to put quotes around the executable of a command. 142*b0d17251SchristosI<This must never ever be done on VMS.> 143*b0d17251Schristos 144*b0d17251Schristos=back 145*b0d17251Schristos 146*b0d17251Schristos=cut 147*b0d17251Schristos 148*b0d17251Schristossub fixup_cmd_elements { 149*b0d17251Schristos # A formatter for the command arguments, defaulting to the Unix setup 150*b0d17251Schristos my $arg_formatter = 151*b0d17251Schristos sub { $_ = shift; 152*b0d17251Schristos ($_ eq '' || /\s|[\{\}\\\$\[\]\*\?\|\&:;<>]/) ? "'$_'" : $_ }; 153*b0d17251Schristos 154*b0d17251Schristos if ( $^O eq "VMS") { # VMS setup 155*b0d17251Schristos $arg_formatter = sub { 156*b0d17251Schristos $_ = shift; 157*b0d17251Schristos if ($_ eq '' || /\s|[!"[:upper:]]/) { 158*b0d17251Schristos s/"/""/g; 159*b0d17251Schristos '"'.$_.'"'; 160*b0d17251Schristos } else { 161*b0d17251Schristos $_; 162*b0d17251Schristos } 163*b0d17251Schristos }; 164*b0d17251Schristos } elsif ( $^O eq "MSWin32") { # MSWin setup 165*b0d17251Schristos $arg_formatter = sub { 166*b0d17251Schristos $_ = shift; 167*b0d17251Schristos if ($_ eq '' || /\s|["\|\&\*\;<>]/) { 168*b0d17251Schristos s/(["\\])/\\$1/g; 169*b0d17251Schristos '"'.$_.'"'; 170*b0d17251Schristos } else { 171*b0d17251Schristos $_; 172*b0d17251Schristos } 173*b0d17251Schristos }; 174*b0d17251Schristos } 175*b0d17251Schristos 176*b0d17251Schristos return ( map { $arg_formatter->($_) } @_ ); 177*b0d17251Schristos} 178*b0d17251Schristos 179*b0d17251Schristos=over 4 180*b0d17251Schristos 181*b0d17251Schristos=item fixup_cmd LIST 182*b0d17251Schristos 183*b0d17251SchristosThis is a sibling of fixup_cmd_elements() that expects the LIST to be a 184*b0d17251Schristoscomplete command line. It does the same thing as fixup_cmd_elements(), 185*b0d17251Schristosexpect that it treats the first LIST element specially on VMS. 186*b0d17251Schristos 187*b0d17251Schristos=back 188*b0d17251Schristos 189*b0d17251Schristos=cut 190*b0d17251Schristos 191*b0d17251Schristossub fixup_cmd { 192*b0d17251Schristos return fixup_cmd_elements(@_) unless $^O eq 'VMS'; 193*b0d17251Schristos 194*b0d17251Schristos # The rest is VMS specific 195*b0d17251Schristos my $prog = shift; 196*b0d17251Schristos 197*b0d17251Schristos # On VMS, running random executables without having a command symbol 198*b0d17251Schristos # means running them with the MCR command. This is an old PDP-11 199*b0d17251Schristos # command that stuck around. 200*b0d17251Schristos # This assumes that we're passed the name of an executable. This is a 201*b0d17251Schristos # safe assumption for OpenSSL command lines 202*b0d17251Schristos my $prefix = 'MCR'; 203*b0d17251Schristos 204*b0d17251Schristos if ($prog =~ /^MCR$/i) { 205*b0d17251Schristos # If the first element is "MCR" (independent of case) already, then 206*b0d17251Schristos # we assume that the program it runs is already written the way it 207*b0d17251Schristos # should, and just grab it. 208*b0d17251Schristos $prog = shift; 209*b0d17251Schristos } else { 210*b0d17251Schristos # If the command itself doesn't have a directory spec, make sure 211*b0d17251Schristos # that there is one. Otherwise, MCR assumes that the program 212*b0d17251Schristos # resides in SYS$SYSTEM: 213*b0d17251Schristos $prog = '[]' . $prog unless $prog =~ /^(?:[\$a-z0-9_]+:)?[<\[]/i; 214*b0d17251Schristos } 215*b0d17251Schristos 216*b0d17251Schristos return ( $prefix, $prog, fixup_cmd_elements(@_) ); 217*b0d17251Schristos} 218*b0d17251Schristos 219*b0d17251Schristos=item dump_data REF, OPTS 220*b0d17251Schristos 221*b0d17251SchristosDump the data from REF into a string that can be evaluated into the same 222*b0d17251Schristosdata by Perl. 223*b0d17251Schristos 224*b0d17251SchristosOPTS is the rest of the arguments, expected to be pairs formed with C<< => >>. 225*b0d17251SchristosThe following OPTS keywords are understood: 226*b0d17251Schristos 227*b0d17251Schristos=over 4 228*b0d17251Schristos 229*b0d17251Schristos=item B<delimiters =E<gt> 0 | 1> 230*b0d17251Schristos 231*b0d17251SchristosInclude the outer delimiter of the REF type in the resulting string if C<1>, 232*b0d17251Schristosotherwise not. 233*b0d17251Schristos 234*b0d17251Schristos=item B<indent =E<gt> num> 235*b0d17251Schristos 236*b0d17251SchristosThe indentation of the caller, i.e. an initial value. If not given, there 237*b0d17251Schristoswill be no indentation at all, and the string will only be one line. 238*b0d17251Schristos 239*b0d17251Schristos=back 240*b0d17251Schristos 241*b0d17251Schristos=cut 242*b0d17251Schristos 243*b0d17251Schristossub dump_data { 244*b0d17251Schristos my $ref = shift; 245*b0d17251Schristos # Available options: 246*b0d17251Schristos # indent => callers indentation ( undef for no indentation, 247*b0d17251Schristos # an integer otherwise ) 248*b0d17251Schristos # delimiters => 1 if outer delimiters should be added 249*b0d17251Schristos my %opts = @_; 250*b0d17251Schristos 251*b0d17251Schristos my $indent = $opts{indent} // 1; 252*b0d17251Schristos # Indentation of the whole structure, where applicable 253*b0d17251Schristos my $nlindent1 = defined $opts{indent} ? "\n" . ' ' x $indent : ' '; 254*b0d17251Schristos # Indentation of individual items, where applicable 255*b0d17251Schristos my $nlindent2 = defined $opts{indent} ? "\n" . ' ' x ($indent + 4) : ' '; 256*b0d17251Schristos my %subopts = (); 257*b0d17251Schristos 258*b0d17251Schristos $subopts{delimiters} = 1; 259*b0d17251Schristos $subopts{indent} = $opts{indent} + 4 if defined $opts{indent}; 260*b0d17251Schristos 261*b0d17251Schristos my $product; # Finished product, or reference to a function that 262*b0d17251Schristos # produces a string, given $_ 263*b0d17251Schristos # The following are only used when $product is a function reference 264*b0d17251Schristos my $delim_l; # Left delimiter of structure 265*b0d17251Schristos my $delim_r; # Right delimiter of structure 266*b0d17251Schristos my $separator; # Item separator 267*b0d17251Schristos my @items; # Items to iterate over 268*b0d17251Schristos 269*b0d17251Schristos if (ref($ref) eq "ARRAY") { 270*b0d17251Schristos if (scalar @$ref == 0) { 271*b0d17251Schristos $product = $opts{delimiters} ? '[]' : ''; 272*b0d17251Schristos } else { 273*b0d17251Schristos $product = sub { 274*b0d17251Schristos dump_data(\$_, %subopts) 275*b0d17251Schristos }; 276*b0d17251Schristos $delim_l = ($opts{delimiters} ? '[' : '').$nlindent2; 277*b0d17251Schristos $delim_r = $nlindent1.($opts{delimiters} ? ']' : ''); 278*b0d17251Schristos $separator = ",$nlindent2"; 279*b0d17251Schristos @items = @$ref; 280*b0d17251Schristos } 281*b0d17251Schristos } elsif (ref($ref) eq "HASH") { 282*b0d17251Schristos if (scalar keys %$ref == 0) { 283*b0d17251Schristos $product = $opts{delimiters} ? '{}' : ''; 284*b0d17251Schristos } else { 285*b0d17251Schristos $product = sub { 286*b0d17251Schristos quotify1($_) . " => " . dump_data($ref->{$_}, %subopts); 287*b0d17251Schristos }; 288*b0d17251Schristos $delim_l = ($opts{delimiters} ? '{' : '').$nlindent2; 289*b0d17251Schristos $delim_r = $nlindent1.($opts{delimiters} ? '}' : ''); 290*b0d17251Schristos $separator = ",$nlindent2"; 291*b0d17251Schristos @items = sort keys %$ref; 292*b0d17251Schristos } 293*b0d17251Schristos } elsif (ref($ref) eq "SCALAR") { 294*b0d17251Schristos $product = defined $$ref ? quotify1 $$ref : "undef"; 295*b0d17251Schristos } else { 296*b0d17251Schristos $product = defined $ref ? quotify1 $ref : "undef"; 297*b0d17251Schristos } 298*b0d17251Schristos 299*b0d17251Schristos if (ref($product) eq "CODE") { 300*b0d17251Schristos $delim_l . join($separator, map { &$product } @items) . $delim_r; 301*b0d17251Schristos } else { 302*b0d17251Schristos $product; 303*b0d17251Schristos } 304*b0d17251Schristos} 305*b0d17251Schristos 306*b0d17251Schristos=back 307*b0d17251Schristos 308*b0d17251Schristos=cut 309*b0d17251Schristos 310*b0d17251Schristos1; 311