xref: /openssl/test/recipes/90-test_store.t (revision 187952d4)
1#! /usr/bin/env perl
2# Copyright 2016-2023 The OpenSSL Project Authors. All Rights Reserved.
3#
4# Licensed under the Apache License 2.0 (the "License").  You may not use
5# this file except in compliance with the License.  You can obtain a copy
6# in the file LICENSE in the source distribution or at
7# https://www.openssl.org/source/license.html
8
9use File::Spec::Functions;
10use File::Copy;
11use MIME::Base64;
12use OpenSSL::Test qw(:DEFAULT srctop_file srctop_dir bldtop_file bldtop_dir
13                     data_file);
14use OpenSSL::Test::Utils;
15
16my $test_name = "test_store";
17setup($test_name);
18
19require(srctop_file("test", "recipes", "tconversion.pl")); # for test_file_contains()
20
21my $use_md5 = !disabled("md5");
22my $use_des = !(disabled("des") || disabled("legacy")); # also affects 3des and pkcs12 app
23my $use_dsa = !disabled("dsa");
24my $use_ecc = !disabled("ec");
25
26my @noexist_files =
27    ( "test/blahdiblah.pem",
28      "test/blahdibleh.der" );
29my @src_files =
30    ( "test/testx509.pem",
31      "test/testrsa.pem",
32      "test/testrsapub.pem",
33      "test/testcrl.pem",
34      "apps/server.pem" );
35my @data_files =
36    ( "testrsa.msb" );
37push(@data_files,
38     ( "testrsa.pvk" ))
39    unless disabled("legacy") || disabled("rc4");
40my @src_rsa_files =
41    ( "test/testrsa.pem",
42      "test/testrsapub.pem" );
43my @generated_files =
44    (
45     ### generated from the source files
46
47     "testx509.der",
48     "testrsa.der",
49     "testrsapub.der",
50     "testcrl.der",
51
52     ### generated locally
53
54     "rsa-key-pkcs1.pem", "rsa-key-pkcs1.der",
55     "rsa-key-pkcs1-aes128.pem",
56     "rsa-key-pkcs8.pem", "rsa-key-pkcs8.der",
57     "rsa-key-pkcs8-pbes2-sha1.pem", "rsa-key-pkcs8-pbes2-sha1.der",
58     "rsa-key-pkcs8-pbes2-sha256.pem", "rsa-key-pkcs8-pbes2-sha256.der",
59    );
60push(@generated_files, (
61     "rsa-key-pkcs8-pbes1-sha1-3des.pem", "rsa-key-pkcs8-pbes1-sha1-3des.der",
62    )) if $use_des;
63push(@generated_files, (
64     "rsa-key-sha1-3des-sha1.p12", "rsa-key-sha1-3des-sha256.p12",
65     "rsa-key-aes256-cbc-sha256.p12",
66     "rsa-key-md5-des-sha1.p12",
67     "rsa-key-aes256-cbc-md5-des-sha256.p12"
68     )) if $use_des;
69push(@generated_files, (
70     "rsa-key-pkcs8-pbes1-md5-des.pem", "rsa-key-pkcs8-pbes1-md5-des.der"
71     )) if $use_md5 && $use_des;
72push(@generated_files, (
73     "dsa-key-pkcs1.pem", "dsa-key-pkcs1.der",
74     "dsa-key-pkcs1-aes128.pem",
75     "dsa-key-pkcs8.pem", "dsa-key-pkcs8.der",
76     "dsa-key-pkcs8-pbes2-sha1.pem", "dsa-key-pkcs8-pbes2-sha1.der",
77     )) if $use_dsa;
78push(@generated_files, "dsa-key-aes256-cbc-sha256.p12") if $use_dsa && $use_des;
79push(@generated_files, (
80     "ec-key-pkcs1.pem", "ec-key-pkcs1.der",
81     "ec-key-pkcs1-aes128.pem",
82     "ec-key-pkcs8.pem", "ec-key-pkcs8.der",
83     "ec-key-pkcs8-pbes2-sha1.pem", "ec-key-pkcs8-pbes2-sha1.der",
84     )) if $use_ecc;
85push(@generated_files, "ec-key-aes256-cbc-sha256.p12") if $use_ecc && $use_des;
86my %generated_file_files =
87    $^O eq 'linux'
88    ? ( "test/testx509.pem" => "file:testx509.pem",
89        "test/testrsa.pem" => "file:testrsa.pem",
90        "test/testrsapub.pem" => "file:testrsapub.pem",
91        "test/testcrl.pem" => "file:testcrl.pem",
92        "apps/server.pem" => "file:server.pem" )
93    : ();
94my @noexist_file_files =
95    ( "file:blahdiblah.pem",
96      "file:test/blahdibleh.der" );
97
98# There is more than one method to get a 'file:' loader.
99# The default is a built-in provider implementation.
100# However, there is also an engine, specially for testing purposes.
101#
102# @methods is a collection of extra 'openssl storeutl' arguments used to
103# try the different methods.
104my @methods;
105my @prov_method = qw(-provider default);
106push @prov_method, qw(-provider legacy) unless disabled('legacy');
107push @methods, [ @prov_method ];
108push @methods, [qw(-engine loader_attic)]
109    unless disabled('loadereng');
110
111my $n = 4 + scalar @methods
112    * ( (3 * scalar @noexist_files)
113        + (6 * scalar @src_files)
114        + (2 * scalar @data_files)
115        + (4 * scalar @generated_files)
116        + (scalar keys %generated_file_files)
117        + (scalar @noexist_file_files)
118        + 3
119        + 11 );
120
121# Test doesn't work under msys because the file name munging doesn't work
122# correctly with the "ot:" prefix
123my $do_test_ossltest_store =
124    !(disabled("engine") || disabled("dynamic-engine") || $^O =~ /^msys$/);
125
126if ($do_test_ossltest_store) {
127    # test loading with apps 'org.openssl.engine:' loader, using the
128    # ossltest engine.
129    $n += 4 * scalar @src_rsa_files;
130}
131
132plan skip_all => "No plan" if $n == 0;
133
134plan tests => $n;
135
136my $test_x509 = srctop_file('test', 'testx509.pem');
137
138ok(run(app(["openssl", "storeutl",  "-crls", $test_x509])),
139   "storeutil with -crls option");
140
141ok(!run(app(["openssl", "storeutl", $test_x509, "-crls"])),
142   "storeutil with extra parameter (at end) should fail");
143
144indir "store_$$" => sub {
145    if ($do_test_ossltest_store) {
146        # ossltest loads PEM files, with names prefixed with 'ot:'.
147        # This prefix ensures that the files are, in fact, loaded through
148        # that engine and not mistakenly going through the 'file:' loader.
149
150        my $engine_scheme = 'org.openssl.engine:';
151        $ENV{OPENSSL_ENGINES} = bldtop_dir("engines");
152
153        foreach (@src_rsa_files) {
154            my $file = srctop_file($_);
155            my $file_abs = to_abs_file($file);
156            my @pubin = $_ =~ m|pub\.pem$| ? ("-pubin") : ();
157
158            ok(run(app(["openssl", "rsa", "-text", "-noout", @pubin,
159                        "-engine", "ossltest", "-inform", "engine",
160                        "-in", "ot:$file"])));
161            ok(run(app(["openssl", "rsa", "-text", "-noout", @pubin,
162                        "-engine", "ossltest", "-inform", "engine",
163                        "-in", "ot:$file_abs"])));
164            ok(run(app(["openssl", "rsa", "-text", "-noout", @pubin,
165                        "-in", "${engine_scheme}ossltest:ot:$file"])));
166            ok(run(app(["openssl", "rsa", "-text", "-noout", @pubin,
167                        "-in", "${engine_scheme}ossltest:ot:$file_abs"])));
168        }
169    }
170
171 SKIP:
172    {
173        init() or die "init failed";
174
175        my $rehash = init_rehash();
176
177        ok(run(app(["openssl", "storeutl", "-out", "cacert.pem", "cacert.pem"])),
178            "identical infile and outfile");
179        test_file_contains("storeutl output on same input",
180                           "cacert.pem", "Total found: 1");
181
182        foreach my $method (@methods) {
183            my @storeutl = ( qw(openssl storeutl), @$method );
184
185            foreach (@noexist_files) {
186                my $file = srctop_file($_);
187
188                ok(!run(app([@storeutl, "-noout", $file])));
189                ok(!run(app([@storeutl, "-noout", to_abs_file($file)])));
190                {
191                    local $ENV{MSYS2_ARG_CONV_EXCL} = "file:";
192
193                    ok(!run(app([@storeutl, "-noout",
194                                 to_abs_file_uri($file)])));
195                }
196            }
197            foreach (@src_files) {
198                my $file = srctop_file($_);
199
200                ok(run(app([@storeutl, "-noout", $file])));
201                ok(run(app([@storeutl, "-noout", to_abs_file($file)])));
202              SKIP:
203                {
204                    skip "file: tests disabled on MingW", 4  if $^O =~ /^msys$/;
205
206                    ok(run(app([@storeutl, "-noout",
207                                to_abs_file_uri($file)])));
208                    ok(run(app([@storeutl, "-noout",
209                                to_abs_file_uri($file, 0, "")])));
210                    ok(run(app([@storeutl, "-noout",
211                                to_abs_file_uri($file, 0, "localhost")])));
212                    ok(!run(app([@storeutl, "-noout",
213                                 to_abs_file_uri($file, 0, "dummy")])));
214                }
215            }
216            foreach (@data_files) {
217                my $file = data_file($_);
218
219                ok(run(app([@storeutl, "-noout", "-passin", "pass:password",
220                            $file])));
221                ok(run(app([@storeutl, "-noout", "-passin", "pass:password",
222                            to_abs_file($file)])));
223            }
224            foreach (@generated_files) {
225                ok(run(app([@storeutl, "-noout", "-passin", "pass:password",
226                            $_])));
227                ok(run(app([@storeutl,  "-noout", "-passin", "pass:password",
228                            to_abs_file($_)])));
229
230              SKIP:
231                {
232                    skip "file: tests disabled on MingW", 2  if $^O =~ /^msys$/;
233
234                    ok(run(app([@storeutl, "-noout", "-passin",
235                                "pass:password", to_abs_file_uri($_)])));
236                    ok(!run(app([@storeutl, "-noout", "-passin",
237                                 "pass:password", to_file_uri($_)])));
238                }
239            }
240            foreach (values %generated_file_files) {
241              SKIP:
242                {
243                    skip "file: tests disabled on MingW", 1  if $^O =~ /^msys$/;
244
245                    ok(run(app([@storeutl,  "-noout", $_])));
246                }
247            }
248            foreach (@noexist_file_files) {
249              SKIP:
250                {
251                    skip "file: tests disabled on MingW", 1  if $^O =~ /^msys$/;
252
253                    ok(!run(app([@storeutl,  "-noout", $_])));
254                }
255            }
256            {
257                my $dir = srctop_dir("test", "certs");
258
259                ok(run(app([@storeutl,  "-noout", $dir])));
260                ok(run(app([@storeutl,  "-noout", to_abs_file($dir, 1)])));
261              SKIP:
262                {
263                    skip "file: tests disabled on MingW", 1  if $^O =~ /^msys$/;
264
265                    ok(run(app([@storeutl,  "-noout",
266                                to_abs_file_uri($dir, 1)])));
267                }
268            }
269
270            ok(!run(app([@storeutl, '-noout',
271                         '-subject', '/C=AU/ST=QLD/CN=SSLeay\/rsa test cert',
272                         srctop_file('test', 'testx509.pem')])),
273               "Checking that -subject can't be used with a single file");
274
275            ok(run(app([@storeutl, '-certs', '-noout',
276                        srctop_file('test', 'testx509.pem')])),
277               "Checking that -certs returns 1 object on a certificate file");
278            ok(run(app([@storeutl, '-certs', '-noout',
279                        srctop_file('test', 'testcrl.pem')])),
280               "Checking that -certs returns 0 objects on a CRL file");
281
282            ok(run(app([@storeutl, '-crls', '-noout',
283                        srctop_file('test', 'testx509.pem')])),
284               "Checking that -crls returns 0 objects on a certificate file");
285            ok(run(app([@storeutl, '-crls', '-noout',
286                        srctop_file('test', 'testcrl.pem')])),
287               "Checking that -crls returns 1 object on a CRL file");
288
289          SKIP: {
290              skip "failed rehash initialisation", 6 unless $rehash;
291
292              # subject from testx509.pem:
293              # '/C=AU/ST=QLD/CN=SSLeay\/rsa test cert'
294              # issuer from testcrl.pem:
295              # '/C=US/O=RSA Data Security, Inc./OU=Secure Server Certification Authority'
296              ok(run(app([@storeutl, '-noout',
297                          '-subject', '/C=AU/ST=QLD/CN=SSLeay\/rsa test cert',
298                          catdir(curdir(), 'rehash')])));
299              ok(run(app([@storeutl, '-noout',
300                          '-subject',
301                          '/C=US/O=RSA Data Security, Inc./OU=Secure Server Certification Authority',
302                          catdir(curdir(), 'rehash')])));
303              ok(run(app([@storeutl, '-noout', '-certs',
304                          '-subject', '/C=AU/ST=QLD/CN=SSLeay\/rsa test cert',
305                          catdir(curdir(), 'rehash')])));
306              ok(run(app([@storeutl, '-noout', '-crls',
307                          '-subject', '/C=AU/ST=QLD/CN=SSLeay\/rsa test cert',
308                          catdir(curdir(), 'rehash')])));
309              ok(run(app([@storeutl, '-noout', '-certs',
310                          '-subject',
311                          '/C=US/O=RSA Data Security, Inc./OU=Secure Server Certification Authority',
312                          catdir(curdir(), 'rehash')])));
313              ok(run(app([@storeutl, '-noout', '-crls',
314                          '-subject',
315                          '/C=US/O=RSA Data Security, Inc./OU=Secure Server Certification Authority',
316                          catdir(curdir(), 'rehash')])));
317            }
318        }
319    }
320}, create => 1, cleanup => 1;
321
322sub init {
323    my $cnf = srctop_file('test', 'ca-and-certs.cnf');
324    my $cakey = srctop_file('test', 'certs', 'ca-key.pem');
325    my @std_args = qw(-provider default);
326    push @std_args, qw(-provider legacy)
327        unless disabled('legacy');
328    return (
329            # rsa-key-pkcs1.pem
330            run(app(["openssl", "pkey", @std_args,
331                     "-in", data_file("rsa-key-2432.pem"),
332                     "-out", "rsa-key-pkcs1.pem"]))
333            # rsa-key-pkcs1-aes128.pem
334            && run(app(["openssl", "rsa", @std_args,
335                        "-passout", "pass:password", "-aes128",
336                        "-in", "rsa-key-pkcs1.pem",
337                        "-out", "rsa-key-pkcs1-aes128.pem"]))
338            # dsa-key-pkcs1.pem
339            && (!$use_dsa
340                || run(app(["openssl", "gendsa", @std_args,
341                            "-out", "dsa-key-pkcs1.pem",
342                            data_file("dsaparam.pem")])))
343            # dsa-key-pkcs1-aes128.pem
344            && (!$use_dsa
345                || run(app(["openssl", "dsa", @std_args,
346                            "-passout", "pass:password", "-aes128",
347                            "-in", "dsa-key-pkcs1.pem",
348                            "-out", "dsa-key-pkcs1-aes128.pem"])))
349            # ec-key-pkcs1.pem (one might think that 'genec' would be practical)
350            && (!$use_ecc
351                || run(app(["openssl", "ecparam", @std_args,
352                            "-genkey",
353                            "-name", "prime256v1",
354                            "-out", "ec-key-pkcs1.pem"])))
355            # ec-key-pkcs1-aes128.pem
356            && (!$use_ecc
357                || run(app(["openssl", "ec", @std_args,
358                            "-passout", "pass:password", "-aes128",
359                            "-in", "ec-key-pkcs1.pem",
360                            "-out", "ec-key-pkcs1-aes128.pem"])))
361            # *-key-pkcs8.pem
362            && runall(sub {
363                          my $dstfile = shift;
364                          (my $srcfile = $dstfile)
365                              =~ s/-key-pkcs8\.pem$/-key-pkcs1.pem/i;
366                          run(app(["openssl", "pkcs8", @std_args,
367                                   "-topk8", "-nocrypt",
368                                   "-in", $srcfile, "-out", $dstfile]));
369                      }, grep(/-key-pkcs8\.pem$/, @generated_files))
370            # *-key-pkcs8-pbes1-sha1-3des.pem
371            && runall(sub {
372                          my $dstfile = shift;
373                          (my $srcfile = $dstfile)
374                              =~ s/-key-pkcs8-pbes1-sha1-3des\.pem$
375                                  /-key-pkcs8.pem/ix;
376                          run(app(["openssl", "pkcs8", @std_args,
377                                   "-topk8",
378                                   "-passout", "pass:password",
379                                   "-v1", "pbeWithSHA1And3-KeyTripleDES-CBC",
380                                   "-in", $srcfile, "-out", $dstfile]));
381                      }, grep(/-key-pkcs8-pbes1-sha1-3des\.pem$/, @generated_files))
382            # *-key-pkcs8-pbes1-md5-des.pem
383            && runall(sub {
384                          my $dstfile = shift;
385                          (my $srcfile = $dstfile)
386                              =~ s/-key-pkcs8-pbes1-md5-des\.pem$
387                                  /-key-pkcs8.pem/ix;
388                          run(app(["openssl", "pkcs8", @std_args,
389                                   "-topk8",
390                                   "-passout", "pass:password",
391                                   "-v1", "pbeWithSHA1And3-KeyTripleDES-CBC",
392                                   "-in", $srcfile, "-out", $dstfile]));
393                      }, grep(/-key-pkcs8-pbes1-md5-des\.pem$/, @generated_files))
394            # *-key-pkcs8-pbes2-sha1.pem
395            && runall(sub {
396                          my $dstfile = shift;
397                          (my $srcfile = $dstfile)
398                              =~ s/-key-pkcs8-pbes2-sha1\.pem$
399                                  /-key-pkcs8.pem/ix;
400                          run(app(["openssl", "pkcs8", @std_args,
401                                   "-topk8",
402                                   "-passout", "pass:password",
403                                   "-v2", "aes256", "-v2prf", "hmacWithSHA1",
404                                   "-in", $srcfile, "-out", $dstfile]));
405                      }, grep(/-key-pkcs8-pbes2-sha1\.pem$/, @generated_files))
406            # *-key-pkcs8-pbes2-sha1.pem
407            && runall(sub {
408                          my $dstfile = shift;
409                          (my $srcfile = $dstfile)
410                              =~ s/-key-pkcs8-pbes2-sha256\.pem$
411                                  /-key-pkcs8.pem/ix;
412                          run(app(["openssl", "pkcs8", @std_args,
413                                   "-topk8",
414                                   "-passout", "pass:password",
415                                   "-v2", "aes256", "-v2prf", "hmacWithSHA256",
416                                   "-in", $srcfile, "-out", $dstfile]));
417                      }, grep(/-key-pkcs8-pbes2-sha256\.pem$/, @generated_files))
418            # *-cert.pem (intermediary for the .p12 inits)
419            && run(app(["openssl", "req", "-x509", @std_args,
420                        "-config", $cnf, "-reqexts", "v3_ca", "-noenc",
421                        "-key", $cakey, "-out", "cacert.pem"]))
422            && runall(sub {
423                          my $srckey = shift;
424                          (my $dstfile = $srckey) =~ s|-key-pkcs8\.|-cert.|;
425                          (my $csr = $dstfile) =~ s|\.pem|.csr|;
426
427                          (run(app(["openssl", "req", "-new", @std_args,
428                                    "-config", $cnf, "-section", "userreq",
429                                    "-key", $srckey, "-out", $csr]))
430                           &&
431                           run(app(["openssl", "x509", @std_args,
432                                    "-days", "3650",
433                                    "-CA", "cacert.pem",
434                                    "-CAkey", $cakey,
435                                    "-set_serial", time(), "-req",
436                                    "-in", $csr, "-out", $dstfile])));
437                      }, grep(/-key-pkcs8\.pem$/, @generated_files))
438            # *.p12
439            && runall(sub {
440                          my $dstfile = shift;
441                          my ($type, $certpbe_index, $keypbe_index,
442                              $macalg_index) =
443                              $dstfile =~ m{^(.*)-key-(?|
444                                                # cert and key PBE are same
445                                                ()             #
446                                                ([^-]*-[^-]*)- # key & cert PBE
447                                                ([^-]*)        # MACalg
448                                            |
449                                                # cert and key PBE are not same
450                                                ([^-]*-[^-]*)- # cert PBE
451                                                ([^-]*-[^-]*)- # key PBE
452                                                ([^-]*)        # MACalg
453                                            )\.}x;
454                          if (!$certpbe_index) {
455                              $certpbe_index = $keypbe_index;
456                          }
457                          my $srckey = "$type-key-pkcs8.pem";
458                          my $srccert = "$type-cert.pem";
459                          my %pbes =
460                              (
461                               "sha1-3des" => "pbeWithSHA1And3-KeyTripleDES-CBC",
462                               "md5-des" => "pbeWithMD5AndDES-CBC",
463                               "aes256-cbc" => "AES-256-CBC",
464                              );
465                          my %macalgs =
466                              (
467                               "sha1" => "SHA1",
468                               "sha256" => "SHA256",
469                              );
470                          my $certpbe = $pbes{$certpbe_index};
471                          my $keypbe = $pbes{$keypbe_index};
472                          my $macalg = $macalgs{$macalg_index};
473                          if (!defined($certpbe) || !defined($keypbe)
474                              || !defined($macalg)) {
475                              print STDERR "Cert PBE for $certpbe_index not defined\n"
476                                  unless defined $certpbe;
477                              print STDERR "Key PBE for $keypbe_index not defined\n"
478                                  unless defined $keypbe;
479                              print STDERR "MACALG for $macalg_index not defined\n"
480                                  unless defined $macalg;
481                              print STDERR "(destination file was $dstfile)\n";
482                              return 0;
483                          }
484                          run(app(["openssl", "pkcs12", @std_args,
485                                   "-inkey", $srckey,
486                                   "-in", $srccert, "-passout", "pass:password",
487                                   "-chain", "-CAfile", "cacert.pem",
488                                   "-export", "-macalg", $macalg,
489                                   "-certpbe", $certpbe, "-keypbe", $keypbe,
490                                   "-out", $dstfile]));
491                      }, grep(/\.p12/, @generated_files))
492            # *.der (the end all init)
493            && runall(sub {
494                          my $dstfile = shift;
495                          (my $srcfile = $dstfile) =~ s/\.der$/.pem/i;
496                          if (! -f $srcfile) {
497                              $srcfile = srctop_file("test", $srcfile);
498                          }
499                          my $infh;
500                          unless (open $infh, $srcfile) {
501                              return 0;
502                          }
503                          my $l;
504                          while (($l = <$infh>) !~ /^-----BEGIN\s/
505                                 || $l =~ /^-----BEGIN.*PARAMETERS-----/) {
506                          }
507                          my $b64 = "";
508                          while (($l = <$infh>) !~ /^-----END\s/) {
509                              $l =~ s|\R$||;
510                              $b64 .= $l unless $l =~ /:/;
511                          }
512                          close $infh;
513                          my $der = decode_base64($b64);
514                          unless (length($b64) / 4 * 3 - length($der) < 3) {
515                              print STDERR "Length error, ",length($b64),
516                                  " bytes of base64 became ",length($der),
517                                  " bytes of der? ($srcfile => $dstfile)\n";
518                              return 0;
519                          }
520                          my $outfh;
521                          unless (open $outfh, ">:raw", $dstfile) {
522                              return 0;
523                          }
524                          print $outfh $der;
525                          close $outfh;
526                          return 1;
527                      }, grep(/\.der$/, @generated_files))
528            && runall(sub {
529                          my $srcfile = shift;
530                          my $dstfile = $generated_file_files{$srcfile};
531
532                          unless (copy srctop_file($srcfile), $dstfile) {
533                              warn "$!\n";
534                              return 0;
535                          }
536                          return 1;
537                      }, keys %generated_file_files)
538           );
539}
540
541sub init_rehash {
542    return (
543            mkdir(catdir(curdir(), 'rehash'))
544            && copy(srctop_file('test', 'testx509.pem'),
545                    catdir(curdir(), 'rehash'))
546            && copy(srctop_file('test', 'testcrl.pem'),
547                    catdir(curdir(), 'rehash'))
548            && run(app(['openssl', 'rehash', catdir(curdir(), 'rehash')]))
549           );
550}
551
552sub runall {
553    my ($function, @items) = @_;
554
555    foreach (@items) {
556        return 0 unless $function->($_);
557    }
558    return 1;
559}
560
561# According to RFC8089, a relative file: path is invalid.  We still produce
562# them for testing purposes.
563sub to_file_uri {
564    my ($file, $isdir, $authority) = @_;
565    my $vol;
566    my $dir;
567
568    die "to_file_uri: No file given\n" if !defined($file) || $file eq '';
569
570    ($vol, $dir, $file) = File::Spec->splitpath($file, $isdir // 0);
571
572    # Make sure we have a Unix style directory.
573    $dir = join('/', File::Spec->splitdir($dir));
574    # Canonicalise it (note: it seems to be only needed on Unix)
575    while (1) {
576        my $newdir = $dir;
577        $newdir =~ s|/[^/]*[^/\.]+[^/]*/\.\./|/|g;
578        last if $newdir eq $dir;
579        $dir = $newdir;
580    }
581    # Take care of the corner cases the loop can't handle, and that $dir
582    # ends with a / unless it's empty
583    $dir =~ s|/[^/]*[^/\.]+[^/]*/\.\.$|/|;
584    $dir =~ s|^[^/]*[^/\.]+[^/]*/\.\./|/|;
585    $dir =~ s|^[^/]*[^/\.]+[^/]*/\.\.$||;
586    if ($isdir // 0) {
587        $dir =~ s|/$|| if $dir ne '/';
588    } else {
589        $dir .= '/' if $dir ne '' && $dir !~ m|/$|;
590    }
591
592    # If the file system has separate volumes (at present, Windows and VMS)
593    # we need to handle them.  In URIs, they are invariably the first
594    # component of the path, which is always absolute.
595    # On VMS, user:[foo.bar] translates to /user/foo/bar
596    # On Windows, c:\Users\Foo translates to /c:/Users/Foo
597    if ($vol ne '') {
598        $vol =~ s|:||g if ($^O eq "VMS");
599        $dir = '/' . $dir if $dir ne '' && $dir !~ m|^/|;
600        $dir = '/' . $vol . $dir;
601    }
602    $file = $dir . $file;
603
604    return "file://$authority$file" if defined $authority;
605    return "file:$file";
606}
607
608sub to_abs_file {
609    my ($file) = @_;
610
611    return File::Spec->rel2abs($file);
612}
613
614sub to_abs_file_uri {
615    my ($file, $isdir, $authority) = @_;
616
617    die "to_abs_file_uri: No file given\n" if !defined($file) || $file eq '';
618    return to_file_uri(to_abs_file($file), $isdir, $authority);
619}
620