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