File Coverage

blib/lib/App/Fetchware/Util.pm
Criterion Covered Total %
statement 299 418 71.5
branch 108 230 46.9
condition 50 92 54.3
subroutine 40 45 88.8
pod 21 21 100.0
total 518 806 64.2


line stmt bran cond sub pod time code
1             package App::Fetchware::Util;
2             $App::Fetchware::Util::VERSION = '1.014';
3             # ABSTRACT: Miscelaneous functions for App::Fetchware.
4             ###BUGALERT### Uses die instead of croak. croak is the preferred way of throwing
5             #exceptions in modules. croak says that the caller was the one who caused the
6             #error not the specific code that actually threw the error.
7 51     51   585884 use strict;
  51         116  
  51         2255  
8 51     51   287 use warnings;
  51         105  
  51         2090  
9              
10 51         4777 use File::Spec::Functions qw(catfile catdir splitpath splitdir rel2abs
11 51     51   9574 file_name_is_absolute rootdir tmpdir);
  51         2158  
12 51     51   2723 use Path::Class;
  51         162299  
  51         3050  
13 51     51   64268 use Net::FTP;
  51         2396982  
  51         3629  
14 51     51   68702 use HTTP::Tiny;
  51         1601337  
  51         3650  
15 51     51   645 use Perl::OSType 'is_os_type';
  51         112  
  51         3080  
16 51     51   392 use Cwd;
  51         111  
  51         3363  
17 51     51   17585 use App::Fetchware::Config ':CONFIG';
  51         150  
  51         9880  
18 51     51   311 use File::Copy 'cp';
  51         116  
  51         3931  
19 51     51   273 use File::Temp 'tempdir';
  51         98  
  51         2146  
20 51     51   368 use File::stat;
  51         99  
  51         672  
21 51     51   2980 use Fcntl qw(S_ISDIR :flock S_IMODE);
  51         91  
  51         9425  
22             # Privileges::Drop only works on Unix, so only load it on Unix.
23 51     51   61551 use if is_os_type('Unix'), 'Privileges::Drop';
  51         544  
  51         330  
24 51     51   364136 use POSIX '_exit';
  51         211  
  51         630  
25 51     51   58338 use Sub::Mage;
  51         476747  
  51         625  
26 51     51   66622 use URI::Split qw(uri_split uri_join);
  51         135887  
  51         3906  
27 51     51   56338 use Text::ParseWords 'quotewords';
  51         74691  
  51         3272  
28 51     51   362 use Data::Dumper;
  51         130  
  51         2403  
29              
30             # Enable Perl 6 knockoffs, and use 5.10.1, because smartmatching and other
31             # things in 5.10 were changed in 5.10.1+.
32 51     51   5771 use 5.010001;
  51         193  
  51         3394  
33              
34             # Set up Exporter to bring App::Fetchware::Util's API to everyone who use's it.
35 51     51   292 use Exporter qw( import );
  51         288  
  51         59108  
36              
37             our %EXPORT_TAGS = (
38             UTIL => [qw(
39             msg
40             vmsg
41             run_prog
42             no_mirror_download_dirlist
43             download_dirlist
44             ftp_download_dirlist
45             http_download_dirlist
46             file_download_dirlist
47             no_mirror_download_file
48             download_file
49             download_ftp_url
50             download_http_url
51             download_file_url
52             do_nothing
53             safe_open
54             drop_privs
55             write_dropprivs_pipe
56             read_dropprivs_pipe
57             create_tempdir
58             original_cwd
59             cleanup_tempdir
60             )],
61             );
62              
63             # create_config_options
64              
65             # *All* entries in @EXPORT_TAGS must also be in @EXPORT_OK.
66             our @EXPORT_OK = map {@{$_}} values %EXPORT_TAGS;
67              
68              
69              
70              
71              
72              
73              
74              
75             ###BUGALERT### Add Test::Wrap support to msg() and vmsg() so that they will
76             #inteligently rewrap any text they receive so newly filled in variables won't
77             #screw up the wrapping.
78             sub msg (@) {
79              
80             # If fetchware was not run in quiet mode, -q.
81 2164 100 100 2164 1 26129 unless (defined $fetchware::quiet and $fetchware::quiet > 0) {
82             # print are arguments. Use say if the last one doesn't end with a
83             # newline. $#_ is the last subscript of the @_ variable.
84 2162 100       20189 if ($_[$#_] =~ /\w*\n\w*\z/) {
85 92         13370 print @_;
86             } else {
87 2070         387040 say @_;
88             }
89             # Quiet mode is turned on.
90             } else {
91             # Don't print anything.
92 2         48 return;
93             }
94             }
95              
96              
97              
98             sub vmsg (@) {
99              
100             # If fetchware was not run in quiet mode, -q.
101             ###BUGALERT### Can I do something like:
102             #eval "use constant quiet => 0;" so that the iffs below can be resolved at
103             #run-time to make vmsg() and msg() faster???
104 4559 100 100 4559 1 60464 unless (defined $fetchware::quiet and $fetchware::quiet > 0) {
105             # If verbose is also turned on.
106 4557 100 100     48735 if (defined $fetchware::verbose and $fetchware::verbose > 0) {
107             # print our arguments. Use say if the last one doesn't end with a
108             # newline. $#_ is the last subscript of the @_ variable.
109 3844 100       76798 if ($_[$#_] =~ /\w*\n\w*\z/) {
110 1174         257372 print @_;
111             } else {
112 2670         519824 say @_;
113             }
114             }
115             # Quiet mode is turned on.
116             } else {
117             # Don't print anything.
118 2         30 return;
119             }
120             }
121              
122              
123              
124              
125              
126              
127             ###BUGALERT### Add support for dry-run functionality!!!!
128             sub run_prog {
129 162     162 1 29009 my (@args) = @_;
130              
131             # Kill weird "Insecure dependency in system while running with -T switch."
132             # fatal exceptions by clearing the taint flag with a regex. I'm not actually
133             # running in taint mode, but it bizarrely thinks I am.
134 162         1248 for my $arg (@args) {
135 284 50       4079 if ($arg =~ /(.*)/) {
136 284         3472 $arg = $1;
137             } else {
138 0         0 die <
139             php.Fetchwarefile: Match anything pattern match failed! Huh! This shouldn't
140             happen, and is probably a bug.
141             EOD
142             }
143             }
144              
145             # Use Text::ParseWords quotewords() subroutine to deal with spliting the
146             # arguments on whitespace, and to properly quote and keep single and double
147             # quotes.
148 162         499 my $program;
149 162         1239 ($program, @args) = map {quotewords('\s+', 1, $_)} @args;
  284         33747  
150              
151             # If fetchware is run without -q.
152 162 100 100     86272 unless (defined $fetchware::quiet and $fetchware::quiet > 0) {
153 160         1134 local $" = '][';
154 160         1192 vmsg <
155             Running command [$program] with options [@args].
156             EOM
157 160 50       3301778 system($program, @args) == 0 or die <
158             fetchware: run-time error. Fetchware failed to execute the specified program
159 0         0 [$program] with the arguments [@args]. The OS error was [$!], and the return
160             value was [@{[$? >> 8]}]. Please see perldoc App::Fetchware::Diagnostics.
161             EOD
162             # If fetchware is run with -q.
163             } else {
164             # Use a piped open() to capture STDOUT, so that STDOUT is not printed to
165             # the terminal like it usually is therby "quiet"ing it.
166             # If not on Windows use safer open call that doesn't work on Windows.
167 2 50       50 unless (is_os_type('Windows', $^O)) {
168 2 50       31634 open(my $fh, '-|', "$program", @args) or die <
169             fetchware: run-time error. Fetchware failed to execute the specified program
170             while capturing its input to prevent it from being copied to the screen, because
171             you ran fetchware with it's --quite or -q option. The program was [$program],
172             and its arguments were [@args]. OS error [$!], and exit value [$?]. Please see
173             perldoc App::Fetchware::Diagnostics.
174             EOD
175             # Close $fh, to cause perl to wait for the command to do its
176             # outputing to STDOUT.
177 2         354 close $fh;
178             # We're on Windows.
179             } else {
180 0 0       0 open(my $fh, '-|', "$program @args") or die <
181             fetchware: run-time error. Fetchware failed to execute the specified program
182             while capturing its input to prevent it from being copied to the screen, because
183             you ran fetchware with it's --quite or -q option. The program was [$program],
184             and its arguments were [@args]. OS error [$!], and exit value [$?]. Please see
185             perldoc App::Fetchware::Diagnostics.
186             EOD
187             # Close $fh, to cause perl to wait for the command to do its
188             # outputing to STDOUT.
189 0         0 close $fh;
190             }
191             }
192             }
193              
194              
195              
196              
197              
198              
199             ###BUGALERT### All download routines should be modified to use HTTP::Tiny's
200             #iterative download interface so I can write the downloaded files straight to
201             #disk to avoid wasting 20, 30 or 120gigs or so or whatever the file size is in
202             #memory for each downloaded file.
203              
204              
205             sub download_dirlist {
206 3     3 1 18595 my %opts;
207             my $url;
208             # One arg means its a $url.
209 3 50       107 if (@_ == 1) {
    0          
210 3         33 $url = shift;
211             # More than one means it's a PATH, and if it's not a path...
212             } elsif (@_ == 2) {
213 0         0 %opts = @_;
214             # Or your param wasn't PATH
215 0 0 0     0 if (not exists $opts{PATH} and not defined $opts{PATH}) {
216             # Use goto for cool old-school C-style error handling to avoid copy
217             # and pasting or insane nested ifs.
218 0         0 goto PATHERROR;
219             }
220             # ...then it's an error.
221             } else {
222 0         0 PATHERROR: die <
223             App-Fetchware-Util: You can only specify either PATH or URL never both. Only
224             specify one or the other when you call download_dirlist().
225             EOD
226             }
227              
228             # Ensure the user has specified a mirror, because otherwise download_file()
229             # will try to just download a path, and that's not going to work.
230 3 50 33     76 die <
231             App-Fetchware-Util: You only called download_dirlist() with just a PATH
232             parameter, but also failed to specify any mirrors in your configuration. Without
233             any defined mirrors download_dirlist() cannot determine from what host to
234             download your file. Please specify a mirror and try again.
235             EOD
236              
237             # Set up our list of urls that we'll try to download the specified PATH or
238             # URL from.
239 3         30 my @urls = config('mirror');
240             # Add lookup_url's hostname to @urls as a last resort for ftp:// and
241             # http:// URLs, and to allow file:// URLs to work, because oftentimes
242             # specifying a mirror when using a local file:// URL makes no sense, and
243             # requiring users to copy and paste the hostname of their lookup_url into a
244             # mirror option is silly.
245 51     51   2870 use Test::More;
  51         50732  
  51         765  
246 3         21 my ($scheme, $auth, undef, undef, undef) =
247             uri_split(config('lookup_url'));
248             # Skip adding the "hostname" for local (file://) url's, because they don't
249             # have a hostname.
250 3 50       7805 if ($scheme ne 'file') {
251 3         2565 push @urls, uri_join($scheme, $auth, undef, undef, undef);
252             }
253 3 50 33     188 if (exists $opts{PATH}
    50 33        
      33        
254             and defined $opts{PATH}
255             and $opts{PATH}) {
256             # The PATH option means that $url is not a full blown URL, but just a
257             # path without a hostname or scheme portion.
258             # Therefore, we append $url, because the PATH option means it's actually
259             # just a path, so we append it to each @url.
260 0         0 for my $mirror_url (@urls) {
261             # Use URI to replace the current path with the one the caller
262             # specified in the $url parameter.
263 0         0 my ($scheme, $auth, undef, undef, undef) = uri_split($mirror_url);
264 0         0 $mirror_url = uri_join($scheme, $auth, $opts{PATH}, undef, undef);
265             }
266             } elsif (defined $url
267             and $url) {
268             # Add $url to @urls since it too has a hostname. And use unshift
269             # to put it in the first position instead of last if you were to use
270             # push.
271 3         9 unshift @urls, $url;
272              
273             # I must parse out the path portion of the specified URL, because this
274             # path portion will be appended to the mirrors you have specified.
275 3         36 my $url_path = ( uri_split($url) )[2];
276 3         49 for my $mirror_url (@urls) {
277             # If the $mirror_url has no path...
278 9         101 my ($scheme, $auth, $path, $query, $frag) =
279             uri_split($mirror_url);
280 9 100       2155 if ($path eq '') {
281             #...then append $url's path.
282             ###BUGALERT## As shown before I was using URI's much nicer
283             #interface, but it was deleting the path instead of replacing
284             #the path! I tried reproducing this with a small test file, but
285             #it worked just fine in the small test file. So, it must be some
286             #really weird bug to fail here, but work in a smaller test file.
287             #I don't know try replacing all of the URI::Split calls with the
288             #equivelent URI->path() calls, and you'll get the weird bug.
289             #$mirror_url->path($url_path);
290 6         23 $mirror_url =
291             uri_join($scheme, $auth, $url_path, $query, $frag);
292             # But if the $mirror_url does have a path...
293             } else {
294             #...Then keep the mirrors path intact.
295             #
296             # Because if you specify a path when you define that mirror
297             # chances are you did it, because that mirror stores it in a
298             # different directory. For example Apache is /apache on some
299             # mirrors, but apache.hostname on other mirrors.
300             }
301             }
302             }
303              
304 3         102 my $dirlist;
305              
306 3         20 for my $mirror_url (@urls) {
307 3         15 eval {
308 3         45 msg "Attempting to download [$mirror_url].";
309             # Try the mirror_url directly without trying any mirrors.
310 3         21 $dirlist = no_mirror_download_dirlist($mirror_url);
311             };
312 3 50       19 if ($@) {
313 0         0 msg "Directory download attempt failed! Error was[";
314 0         0 print $@;
315 0         0 msg "].";
316             }
317              
318             # Skip the rest of the @urls after we successfully download the $url.
319 3 50       25 if (defined $dirlist) {
320 3         14 msg "Successfully downloaded the directory listing.";
321 3         38 last;
322             }
323             }
324              
325 3 50       33 die <
326 0         0 App-Fetchware-Util: Failed to download the specifed URL [$url] or path
327             [$opts{PATH}] using the included hostname in the url you specifed or any
328             mirrors. The mirrors are [@{[config('mirror')]}]. And the urls
329             that fetchware tried to download were [@urls].
330             EOD
331              
332 3         89 return $dirlist;
333             }
334              
335              
336              
337             sub no_mirror_download_dirlist {
338 3     3 1 10 my $url = shift;
339              
340 3         6 my $dirlist;
341 3 50       63 if ($url =~ m!^ftp://.*$!) {
    50          
    50          
342 0         0 $dirlist = ftp_download_dirlist($url);
343             } elsif ($url =~ m!^http://.*$!) {
344 0         0 $dirlist = http_download_dirlist($url);
345             } elsif ($url =~ m!^file://.*$!) {
346 3         16 $dirlist = file_download_dirlist($url);
347             } else {
348 0         0 die <
349             App-Fetchware: run-time syntax error: the url parameter your provided in
350             your call to download_dirlist() [$url] does not have a supported URL scheme (the
351             http:// or ftp:// part). The only supported download types, schemes, are FTP and
352             HTTP. See perldoc App::Fetchware.
353             EOD
354             }
355              
356 3         18 return $dirlist;
357             }
358              
359              
360              
361             sub ftp_download_dirlist {
362 0     0 1 0 my $ftp_url = shift;
363 0         0 $ftp_url =~ m!^ftp://([-a-z,A-Z,0-9,\.]+)(/.*)?!;
364 0         0 my $site = $1;
365 0         0 my $path = $2;
366              
367             # Add debugging later based on fetchware commandline args.
368             # for debugging: $ftp = Net::FTP->new('$site','Debug' => 10);
369             # open a connection and log in!
370 0         0 my $ftp;
371 0 0       0 $ftp = Net::FTP->new($site)
372             or die <
373             App-Fetchware: run-time error. fetchware failed to connect to the ftp server at
374             domain [$site]. The system error was [$@].
375             See man App::Fetchware.
376             EOD
377              
378 0 0       0 $ftp->login("anonymous",'-anonymous@')
379             or die <
380             App-Fetchware: run-time error. fetchware failed to log in to the ftp server at
381 0         0 domain [$site]. The ftp error was [@{[$ftp->message]}]. See man App::Fetchware.
382             EOD
383              
384              
385 0 0       0 my @dir_listing = $ftp->dir($path)
386             or die <
387             App-Fetchware: run-time error. fetchware failed to get a long directory listing
388 0         0 of [$path] on server [$site]. The ftp error was [@{[$ftp->message]}]. See man App::Fetchware.
389             EOD
390              
391 0         0 $ftp->quit();
392              
393 0         0 return \@dir_listing;
394             }
395              
396              
397              
398             sub http_download_dirlist {
399 0     0 1 0 my $http_url = shift;
400              
401             # Forward any other options over to HTTP::Tiny. This is used mostly to
402             # support changing user agent strings, but why not support them all.
403 0 0       0 my %opts = @_ if @_ % 2 == 0;
404              
405             # Append user_agent if specified.
406 0 0       0 $opts{agent} = config('user_agent') if config('user_agent');
407              
408 0         0 my $http = HTTP::Tiny->new(%opts);
409             ###BUGALERT### Should use request() instead of get, because request can
410             #directly write the chunks of the file to disk as they are downloaded. get()
411             #just uses RAM, so a 50Meg file takes up 50 megs of ram, and so on.
412             ###BUGALERT### Also, if you use request instead, and get chunks of bytes
413             #instead of just writing them to disk, you could also use a
414             #Term::ProgressBar to print a cool progress bar during the download!
415             #This could also be added to the ftp downloaders too, but probably not the
416             #local file:// downloaders though.
417 0         0 my $response = $http->get($http_url);
418              
419 0 0       0 die <{success};
420             App-Fetchware: run-time error. HTTP::Tiny failed to download a directory listing
421 0         0 of your provided lookup_url. HTTP status code [$response->{status} $response->{reason}]
422             HTTP headers [@{[Data::Dumper::Dumper($response)]}].
423             See man App::Fetchware.
424             EOD
425              
426              
427 0         0 while (my ($k, $v) = each %{$response->{headers}}) {
  0         0  
428 0 0       0 for (ref $v eq 'ARRAY' ? @$v : $v) {
429             }
430             }
431              
432 0 0       0 die <{content};
433             App-Fetchware: run-time error. The lookup_url you provided downloaded nothing.
434 0         0 HTTP status code [$response->{status} $response->{reason}]
435             HTTP headers [@{[Data::Dumper::Dumper($response)]}].
436             See man App::Fetchware.
437             EOD
438 0         0 return $response->{content};
439             }
440              
441              
442              
443             sub file_download_dirlist {
444 9     9 1 1192 my $local_lookup_url = shift;
445              
446 9         47 $local_lookup_url =~ s!^file://!!; # Strip scheme garbage.
447              
448             # Prepend original_cwd() if $local_lookup_url is a relative path.
449 9 50       61 unless (file_name_is_absolute($local_lookup_url)) {
450 0         0 $local_lookup_url = catdir(original_cwd(), $local_lookup_url);
451             }
452              
453             # Throw an exception if called with a directory that does not exist.
454 9 100       451 die <
455             App-Fetchware-Util: The directory that fetchware is trying to use to determine
456             if a new version of the software is available does not exist. This directory is
457             [$local_lookup_url], and the OS error is [$!].
458             EOD
459              
460              
461 7         16 my @file_listing;
462 7 50       478 opendir my $dh, $local_lookup_url or die <
463             App-Fetchware-Util: The directory that fetchware is trying to use to determine
464             if a new version of the software is availabe cannot be opened. This directory is
465             [$local_lookup_url], and the OS error is [$!].
466             EOD
467 7         443 while (my $filename = readdir($dh)) {
468             # Trim the useless '.' and '..' Unix convention fake files from the listing.
469 244 100 100     921 unless ($filename eq '.' or $filename eq '..') {
470             # Turn the relative filename into a full pathname.
471             #
472             # Full pathnames are required, because lookup()'s
473             # file_parse_filelist() stat()s each file using just their filename,
474             # and if it's relative instead of absolute these stat() checks will
475             # fail.
476 230         806 my $full_path = catfile($local_lookup_url, $filename);
477 230         839 push @file_listing, $full_path;
478             }
479             }
480              
481 7         134 closedir $dh;
482              
483             # Throw another exception if the directory contains nothing.
484             # Awesome, clever, and simple Path::Class based "is dir empty" test courtesy
485             # of tobyinc on PerlMonks (http://www.perlmonks.org/?node_id=934482).
486 7         58 my $pc_local_lookup_url = dir($local_lookup_url);
487 7 100 66     1044 die <stat() && !$pc_local_lookup_url->children();
488             App-Fetchware-Util: The directory that fetchware is trying to use to determine
489             if a new version of the software is available is empty. This directory is
490             [$local_lookup_url].
491             EOD
492              
493 5         52940 return \@file_listing;
494             }
495              
496              
497              
498              
499             ###BUGALERT###I'm a 190 line disaster! Please refactor me. Oh, and
500             #download_dirlist() too please, because I'm just a copy and paste of that
501             #subroutine!
502             sub download_file {
503 5     5 1 187 my %opts;
504             my $url;
505             # One arg means its a $url.
506 5 100       33 if (@_ == 1) {
    50          
507 2         6 $url = shift;
508             # More than one means it's a PATH, and if it's not a path...
509             } elsif (@_ == 2) {
510 3         180 %opts = @_;
511             # Or your param wasn't PATH
512 3 0 33     24 if (not exists $opts{PATH} and not defined $opts{PATH}) {
513             # Use goto for cool old-school C-style error handling to avoid copy
514             # and pasting or insane nested ifs.
515 0         0 goto PATHERROR;
516             }
517             # ...then it's an error.
518             } else {
519 0         0 PATHERROR: die <
520             App-Fetchware-Util: You can only specify either PATH or URL never both. Only
521             specify one or the other when you call download_file().
522             EOD
523             }
524              
525             # Ensure the user has specified a mirror, because otherwise download_file()
526             # will try to just download a path, and that's not going to work.
527 5 50 66     39 if (not config('mirror') and exists $opts{PATH}
      66        
528             and (config('lookup_url') !~ m!^file://!)) {
529 0         0 die <
530             App-Fetchware-Util: You only called download_file() with just a PATH parameter,
531             but also failed to specify any mirrors in your configuration. Without any
532             defined mirrors download_file() cannot determine from what host to download your
533             file. Please specify a mirror and try again.
534             EOD
535             }
536              
537             # Set up our list of urls that we'll try to download the specified PATH or
538             # URL from.
539 5         63 my @urls = config('mirror');
540             # If we're called with a PATH option and the lookup_url is for a local file,
541             # then we should just convert from a PATH into a $url.
542 5 100 66     54 if (exists $opts{PATH} and config('lookup_url') =~ m!^file://!) {
    50          
543 3         12 $url = "file://$opts{PATH}";
544 3         13 delete $opts{PATH};
545             # Otherwise, we should add lookup_url's hostname to the list of mirrors, but
546             # be sure to push it onto @urls so that it is used last.
547             #
548             # But only if lookup_url is defined.
549             } elsif (defined config('lookup_url')) {
550 0         0 my ($scheme, $auth, undef, undef, undef) =
551             uri_split(config('lookup_url'));
552 0         0 push @urls, uri_join($scheme, $auth, undef, undef, undef);
553             }
554              
555 5 50 33     221 if (exists $opts{PATH}
    50 33        
      33        
556             and defined $opts{PATH}
557             and $opts{PATH}) {
558             # The PATH option means that $url is not a full blown URL, but just a
559             # path without a hostname or scheme portion.
560             # Therefore, we append $url, because the PATH option means it's actually
561             # just a path, so we append it to each @url.
562 0         0 for my $mirror_url (@urls) {
563             # If the $mirror_url has no path...
564 0         0 my ($scheme, $auth, $path, $query, $frag) =
565             uri_split($mirror_url);
566             # Skip messing with the path if $path eq $opts{PATH}, which means the
567             # current $mirror_url is $url, so we shouldn't add its own path to
568             # itself--we should skip it instead.
569 0 0       0 next if $path eq $opts{PATH};
570 0 0       0 if ($path eq '') {
571             #...then append $url's path.
572             ###BUGALERT## As shown before I was using URI's much nicer
573             #interface, but it was deleting the path instead of replacing
574             #the path! I tried reproducing this with a small test file, but
575             #it worked just fine in the small test file. So, it must be some
576             #really weird bug to fail here, but work in a smaller test file.
577             #I don't know try replacing all of the URI::Split calls with the
578             #equivelent URI->path() calls, and you'll get the weird bug.
579             #$mirror_url->path($opts{PATH});
580             ###Add an unless ($opts{PATH} eq '')
581 0         0 $mirror_url =
582             uri_join($scheme, $auth, $opts{PATH}, $query, $frag);
583             # But if the $mirror_url does have a path...
584             } else {
585             #...Then keep the mirrors path intact.
586             #
587             # Because if you specify a path when you define that mirror
588             # chances are you did it, because that mirror stores it in a
589             # different directory. For example Apache is /apache on some
590             # mirrors, but apache.hostname on other mirrors.
591             #
592             #Except add $path's basename, because otherwise we'll ask
593             #for a dirlisting or try to download a directory as a file.
594 0 0       0 unless ($path =~ m!/$!) {
595 0         0 $mirror_url =
596             uri_join($scheme, $auth, $path . '/'
597             . file($opts{PATH})->basename(), $query, $frag);
598             # Skip adding a '/' if ones already there at the end.
599             } else {
600 0         0 $mirror_url =
601             uri_join($scheme, $auth, $path
602             . file($opts{PATH})->basename(), $query, $frag);
603             }
604             }
605             }
606             } elsif (defined $url
607             and $url) {
608             # Add $url to @urls since it too has a hostname. And use unshift
609             # to put it in the first position instead of last if you were to use
610             # push.
611 5         18 unshift @urls, $url;
612              
613             # I must parse out the path portion of the specified URL, because this
614             # path portion will be appended to the mirrors you have specified.
615 5         77 my $url_path = ( uri_split($url) )[2];
616 5         212 for my $mirror_url (@urls) {
617             # If the $mirror_url has no path...
618 10         28 my ($scheme, $auth, $path, $query, $frag) =
619             uri_split($mirror_url);
620             # Skip messing with the path if $path eq $url_path, which means the
621             # current $mirror_url is $url, so we shouldn't add its own path to
622             # itself--we should skip it instead.
623 10 100       9920 next if $path eq $url_path;
624 5 50       31 if ($path eq '') {
625             #...then append $url's path.
626             ###BUGALERT## As shown before I was using URI's much nicer
627             #interface, but it was deleting the path instead of replacing
628             #the path! I tried reproducing this with a small test file, but
629             #it worked just fine in the small test file. So, it must be some
630             #really weird bug to fail here, but work in a smaller test file.
631             #I don't know try replacing all of the URI::Split calls with the
632             #equivelent URI->path() calls, and you'll get the weird bug.
633             #$mirror_url->path($url_path);
634             ###Add an unless ($url_path eq '')
635 5         136 $mirror_url =
636             uri_join($scheme, $auth, $url_path, $query, $frag);
637             # But if the $mirror_url does have a path...
638             } else {
639             #...Then keep the mirrors path intact.
640             #
641             # Because if you specify a path when you define that mirror
642             # chances are you did it, because that mirror stores it in a
643             # different directory. For example Apache is /apache on some
644             # mirrors, but apache.hostname on other mirrors.
645             #
646             #Except add $path's basename, because otherwise we'll ask
647             #for a dirlisting or try to download a directory as a file.
648 0 0       0 unless ($path =~ m!/$!) {
649 0         0 $mirror_url =
650             uri_join($scheme, $auth, $path . '/'
651             . file($url_path)->basename(), $query, $frag);
652             # Skip adding a '/' if ones already there at the end.
653             } else {
654 0         0 $mirror_url =
655             uri_join($scheme, $auth, $path
656             . file($url_path)->basename(), $query, $frag);
657             }
658             }
659             }
660             }
661              
662 5         186 my $filename;
663              
664 5         17 for my $mirror_url (@urls) {
665 5         14 eval {
666 5         32 msg "Attempting to download [$mirror_url].";
667             # Try the mirror_url directly without trying any mirrors.
668 5         32 $filename = no_mirror_download_file($mirror_url);
669             };
670 5 50       51 if ($@) {
671 0         0 msg "File download attempt failed! Error was[";
672 0         0 print $@;
673 0         0 msg "].";
674             }
675              
676             # Skip the rest of the @urls after we successfully download the $url.
677 5 50       35 if (defined $filename) {
678 5         283 msg "Successfully downloaded the file [$mirror_url].";
679 5         26 last;
680             }
681             }
682              
683 5 50       42 die <
684 0         0 App-Fetchware-Util: Failed to download the specifed URL [$url] or path
685             [$opts{PATH}] using the included hostname in the url you specifed or any
686 0         0 mirrors. The mirrors are [@{[config('mirror')]}]. And the urls
687             that fetchware tried to download were [@{[@urls]}].
688             EOD
689              
690 5         169 return $filename;
691             }
692              
693              
694              
695             sub no_mirror_download_file {
696 5     5 1 516 my $url = shift;
697              
698 5         13 my $filename;
699 5 50       86 if ($url =~ m!^ftp://!) {
    50          
    50          
700 0         0 $filename = download_ftp_url($url);
701             } elsif ($url =~ m!^http://!) {
702 0         0 $filename = download_http_url($url);
703             } elsif ($url =~ m!^file://!) {
704 5         31 $filename = download_file_url($url);
705             } else {
706 0         0 die <
707             App-Fetchware: run-time syntax error: the url parameter your provided in
708             your call to download_file() [$url] does not have a supported URL scheme (the
709             http:// or ftp:// part). The only supported download types, schemes, are FTP and
710             HTTP. See perldoc App::Fetchware.
711             EOD
712             }
713              
714 5         3612 return $filename;
715             }
716              
717              
718              
719             sub download_ftp_url {
720 0     0 1 0 my $ftp_url = shift;
721              
722             ###BUGALERT### Replace custom regex with URI::Split's regex.
723 0         0 $ftp_url =~ m!^ftp://([-a-z,A-Z,0-9,\.]+)(/.*)?!;
724 0         0 my $site = $1;
725 0         0 my $path = $2;
726 0         0 my ($volume, $directories, $file) = splitpath($path);
727              
728             # for debugging: $ftp = Net::FTP->new('site','Debug',10);
729             # open a connection and log in!
730              
731 0 0       0 my $ftp = Net::FTP->new($site)
732             or die <
733             App-Fetchware: run-time error. fetchware failed to connect to the ftp server at
734             domain [$site]. The system error was [$@].
735             See man App::Fetchware.
736             EOD
737            
738 0 0       0 $ftp->login("anonymous",'-anonymous@')
739             or die <
740             App-Fetchware: run-time error. fetchware failed to log in to the ftp server at
741 0         0 domain [$site]. The ftp error was [@{[$ftp->message]}]. See man App::Fetchware.
742             EOD
743              
744             # set transfer mode to binary
745 0 0       0 $ftp->binary()
746             or die <
747             App-Fetchware: run-time error. fetchware failed to swtich to binary mode while
748 0         0 trying to download a the file [$path] from site [$site]. The ftp error was
749             [@{[$ftp->message]}]. See perldoc App::Fetchware.
750             EOD
751              
752             # change the directory on the ftp site
753 0 0       0 $ftp->cwd($directories)
754             or die <
755 0         0 App-Fetchware: run-time error. fetchware failed to cwd() to [$path] on site
756             [$site]. The ftp error was [@{[$ftp->message]}]. See perldoc App::Fetchware.
757             EOD
758              
759              
760             # Download the file to the current directory. The start() subroutine should
761             # have cd()d to a tempdir for fetchware to use.
762 0 0       0 $ftp->get($file)
763             or die <
764 0         0 App-Fetchware: run-time error. fetchware failed to download the file [$file]
765             from path [$path] on server [$site]. The ftp error message was
766             [@{[$ftp->message]}]. See perldoc App::Fetchware.
767             EOD
768              
769             # ftp done!
770 0         0 $ftp->quit;
771              
772             # The caller needs the $filename to determine the $package_path later.
773 0         0 return $file;
774             }
775              
776              
777              
778             sub download_http_url {
779 0     0 1 0 my $http_url = shift;
780              
781             # Forward any other options over to HTTP::Tiny. This is used mostly to
782             # support changing user agent strings, but why not support them all.
783 0 0       0 my %opts = @_ if @_ % 2 == 0;
784              
785             # Append user_agent if specified.
786 0 0       0 $opts{agent} = config('user_agent') if config('user_agent');
787              
788 0         0 my $http = HTTP::Tiny->new(%opts);
789             ###BUGALERT### Should use request() instead of get, because request can
790             #directly write the chunks of the file to disk as they are downloaded. get()
791             #just uses RAM, so a 50Meg file takes up 50 megs of ram, and so on.
792 0         0 my $response = $http->get($http_url);
793              
794             #use Test::More;
795             #diag("RESPONSE OBJECT[");
796             #diag explain $response->{status};
797             #diag explain $response->{headers};
798             #diag explain $response->{url};
799             #diag explain $response->{reason};
800             #diag explain $response->{success};
801             ## Should be commented out to avoid borking the terminal, but is needed when
802             ## HTTP::Tiny has internal 599 errors, because the error message is in the
803             ## content.
804             ##diag explain $response->{content};
805             #diag("]");
806              
807 0 0       0 die <{success};
808             App-Fetchware: run-time error. HTTP::Tiny failed to download a file or directory
809 0         0 listingfrom your provided url [$http_url]. HTTP status code
810             [$response->{status} $response->{reason}] HTTP headers
811             [@{[Data::Dumper::Dumper($response->{headers})]}].
812             See man App::Fetchware.
813             EOD
814              
815             # In this case the content is binary, so it will mess up your terminal.
816             #diag($response->{content}) if length $response->{content};
817 0 0       0 die <{content};
818 0         0 App-Fetchware: run-time error. The url [$http_url] you provided downloaded
819             nothing. HTTP status code [$response->{status} $response->{reason}]
820             HTTP headers [@{[Data::Dumper::Dumper($response)]}].
821             See man App::Fetchware.
822             EOD
823              
824             # Must convert the worthless $response->{content} variable into a real file
825             # on the filesystem. Note: start() should have cd()d us into a suitable
826             # tempdir.
827 0         0 my $path = $http_url;
828 0         0 $path =~ s!^http://!!;
829             # Determine filename from the $path.
830 0         0 my ($volume, $directories, $filename) = splitpath($path);
831             # If $filename is empty string, then its probably a index directory listing.
832 0   0     0 $filename ||= 'index.html';
833             ###BUGALERT### Need binmode() on Windows???
834             ###BUGALERT### Switch to safe_open()????
835 0 0       0 open(my $fh, '>', $filename) or die <
836             App-Fetchware: run-time error. Fetchware failed to open a file necessary for
837             fetchware to store HTTP::Tiny's output. Os error [$!]. See perldoc
838             App::Fetchware.
839             EOD
840             # Write HTTP::Tiny's downloaded file to a real file on the filesystem.
841 0         0 print $fh $response->{content};
842 0 0       0 close $fh
843             or die <
844             App-Fetchware: run-time error. Fetchware failed to close the file it created to
845             save the content it downloaded from HTTP::Tiny. This file was [$filename]. OS
846             error [$!]. See perldoc App::Fetchware.
847             EOS
848              
849             # The caller needs the $filename to determine the $package_path later.
850 0         0 return $filename;
851             }
852              
853              
854              
855              
856             sub download_file_url {
857 5     5 1 13 my $url = shift;
858              
859 5         40 $url =~ s!^file://!!; # Strip useless URL scheme.
860            
861             # Prepend original_cwd() only if the $url is *not* absolute, which will mess
862             # it up.
863 5 50       63 $url = catdir(original_cwd(), $url) unless file_name_is_absolute($url);
864              
865             # Download the file:// URL to the current directory, which should already be
866             # in $temp_dir, because of start()'s chdir().
867             #
868             # Don't forget to clear taint. Fetchware does *not* run in taint mode, but
869             # for some reason, bug?, File::Copy checks if data is tainted, and then
870             # retaints it if it is already tainted, but for some reason I get "Insecure
871             # dependency" taint failure exceptions when drop priving. The fix is to
872             # always untaint my data as done below.
873             ###BUGALERT### Investigate this as a possible taint bug in perl or just
874             #File::Copy. Perhaps the cause is using File::Copy::cp(copy) after drop
875             #priving with data from root?
876 5         102 $url =~ /(.*)/;
877 5         21 my $untainted_url = $1;
878 5         49045 my $cwd = cwd();
879 5         199 $cwd =~ /(.*)/;
880 5         93 my $untainted_cwd = $1;
881 5 50       184 cp($untainted_url, $untainted_cwd) or die <
882             App::Fetchware: run-time error. Fetchware failed to copy the download URL
883             [$untainted_url] to the working directory [$untainted_cwd]. Os error [$!].
884             EOD
885              
886             # Return just file filename of the downloaded file.
887 5         6545 return file($url)->basename();
888             }
889              
890              
891              
892              
893              
894              
895              
896             ###BUGALERT### safe_open() does not check extended file perms such as ext*'s
897             #crazy attributes, linux's (And other Unixs' too) MAC stuff or Windows NT's
898             #crazy file permissions. Could use Win32::Perms for just Windows, but its not
899             #on CPAN. And what about the other OSes.
900             ###BUGALERT### Consier moving this to CPAN??? File::SafeOpen????
901             sub safe_open {
902 163     163 1 2849648 my $file_to_check = shift;
903 163   100     1075 my $open_fail_message = shift // <
904             Failed to open file [$file_to_check]. OS error [$!].
905             EOE
906              
907 163         4862 my %opts = @_;
908              
909 163         424 my $fh;
910              
911              
912             # Open the file first.
913 163 100 66     2349 unless (exists $opts{MODE} and defined $opts{MODE}) {
914 162 50       19706 open $fh, '<', $file_to_check or die $open_fail_message;
915             } else {
916 1 50       163 open $fh, $opts{MODE}, $file_to_check or die $open_fail_message;
917             }
918              
919 163         3086 my $info = stat($fh);# or goto STAT_ERROR;
920              
921             # Owner must be either me (whoever runs fetchware) or superuser. No one else
922             # can be trusted.
923 163 50 33     51599 if(($info->uid() != 0) && ($info->uid() != $<)) {
924 0         0 die <
925             App-Fetchware-Util: The file fetchware attempted to open is not owned by root or
926             the person who ran fetchware. This means the file could have been dangerously
927             altered, or it's a simple permissions problem. Do not simly change the
928             ownership, and rerun fetchware. Please check that the file [$file_to_check] has
929             not been tampered with, correct the ownership problems and try again.
930             EOD
931             }
932              
933             # Check if group and other can write $fh.
934             # Use 066 to detect read or write perms.
935             ###BUGALERT### What does this actually test?????
936 163 100       7015 if ($info->mode() & 022) { # Someone else can write this $fh.
937 2         49 die <
938             App-Fetchware-Util: The file fetchware attempted to open [$file_to_check] is
939             writable by someone other than just the owner. Fetchwarefiles and fetchware
940             packages must only be writable by the owner. Do not only change permissions to
941             fix this error. This error may have allowed someone to alter the contents of
942             your Fetchwarefile or fetchware packages. Ensure the file was not altered, then
943             change permissions to 644.
944             EOD
945             }
946            
947             # Then check the directories its contained in.
948              
949             # Make the file an absolute path if its not already.
950 161         3530 $file_to_check = rel2abs($file_to_check);
951              
952             # Create array of current directory and all parent directories and even root
953             # directory to check all of their permissions below.
954 161         9576 my $dir = dir($file_to_check);
955 161         14782 my @directories = do {
956 161         379 my @dirs;
957 161         4361 until ($dir eq rootdir()) {
958             # Add this dir to the array of dirs to keep.
959 485         49853 push @dirs, $dir;
960              
961             # This loops version of $i++ the itcremeter.
962 485         3394 $dir = $dir->parent();
963             }
964 161         15736 push @dirs, $dir->parent(); # $dir->parent() should be the root dir.
965              
966             # Return, by being the last statement, the list of parent dirs for
967             # $file_to_check.
968 161         12780 @dirs;
969             };
970             # Who cares if _PC_CHOWN_RESTRICTED is set, check all parent dirs anyway,
971             # because if say /home was 777, then anyone (other) can change any child
972             # file in any directory above /home now anyway even if _PC_CHOWN_RESTRICTED
973             # is set.
974 161         983 for my $dir (@directories) {
975              
976 642         18768 my $info = stat($dir);# or goto STAT_ERROR;
977              
978             # Owner must be either me (whoever runs fetchware) or superuser. No one
979             # else can be trusted.
980 642 50 33     113391 if(($info->uid() != 0) && ($info->uid() != $<)) {
981 0         0 die <
982             App-Fetchware-Util: The file fetchware attempted to open is not owned by root or
983             the person who ran fetchware. This means the file could have been dangerously
984             altered, or it's a simple permissions problem. Do not simly change the
985             ownership, and rerun fetchware. Please check that the file [$file_to_check] has
986             not been tampered with, correct the ownership problems and try again.
987             EOD
988             }
989              
990             # Check if group and other can write $fh.
991             # Use 066 to detect read or write perms.
992             ###BUGALERT### What does this actually test?????
993 642 100       18743 if ($info->mode() & 022) { # Someone else can write this $fh...
994             # ...except if this file has the sticky bit set and its a directory.
995 161 100 66     4444 die <mode & 01000 and S_ISDIR($info->mode);
996 2         104 App-Fetchware-Util: The file fetchware attempted to open [$file_to_check] is
997             writable by someone other than just the owner. Fetchwarefiles and fetchware
998             packages must only be writable by the owner. Do not only change permissions to
999             fix this error. This error may have allowed someone to alter the contents of
1000             your Fetchwarefile or fetchware packages. Ensure the file was not altered, then
1001             change permissions to 644. Permissions on failed directory were:
1002 2         1212 @{[Dumper($info)]}
1003             Umask [@{[umask]}].
1004             EOD
1005             }
1006              
1007             }
1008             # Return the proven above "safe" file handle.
1009 159         3386 return $fh;
1010              
1011             # Use cool C style goto error handling. It beats copy and paste, and the
1012             # horrible contortions needed for "structured programming."
1013 0         0 STAT_ERROR: {
1014 0         0 die <
1015             App-Fetchware-Util: stat($fh) filename [$file_to_check] failed! This just
1016             shouldn't happen unless of course the file you specified does not exist. Please
1017             ensure files you specify when you run fetchware actually exist.
1018             EOD
1019             }
1020             }
1021              
1022              
1023              
1024             sub drop_privs {
1025 127     127 1 124540 my $child_code = shift;
1026 127   100     2956 my $regular_user = shift // 'nobody';
1027 127         604 my %opts = @_;
1028              
1029             # Need to do this in 2 places.
1030             my $dont_drop_privs = sub {
1031 2     2   20 my $child_code = shift;
1032              
1033 2         10 my $output;
1034 2 50       122 open my $output_fh, '>', \$output or die <
1035             App-Fetchware-Util: fetchware failed to open an internal scalar reference as a
1036             file handle. OS error [$!].
1037             EOD
1038 2         26 $child_code->($output_fh);
1039              
1040 2 50       94 close $output_fh or die <
1041             App-Fetchware-Util: fetchware failed to close an internal scalar reference that
1042             was open as a file handle. OS error [$!].
1043             EOD
1044 2         30 return \$output;
1045 127         2768 };
1046              
1047             # Execute $child_code without dropping privs if the user's configuration
1048             # file is configured to force fetchware to "stay_root."
1049 127 100       946 if (config('stay_root')) {
1050 2         66 msg <
1051             stay_root is set to true. NOT dropping privileges!
1052             EOM
1053 2         26 return $dont_drop_privs->($child_code);
1054             }
1055              
1056 125 50 33     2604 if (is_os_type('Unix') and ($< == 0 or $> == 0)) {
      33        
1057             # cmd_new() needs to skip the creation of this useless directory that it
1058             # does not use. Furthemore, the creation of this extra tempdir is not
1059             # needed by cmd_new(), and this tempdir presumes start() was called
1060             # before drop_privs(), which is always the case except for cmd_new().
1061             #
1062             # But another case where this temp dir's creations should be skipped is
1063             # if start() is overridden with hook() to make start() do something
1064             # other than create a temp dir, because in some cases such as using VCS
1065             # instead of Web sites and mirrors, you do not need to bother with
1066             # creating a tempdir, because the working dir of the repo can be used
1067             # instead. Therefore, if the parent directory is not /^fetchware-$$/,
1068             # then we'll also skip creating the tempd dir, because it most likely
1069             # means that a tempdir is not needed.
1070 125 100       1617837 $opts{SkipTempDirCreation} = 1
1071             unless file(cwd())->basename() =~ /^fetchware-$$/;
1072 125 50 66     73124 unless (exists $opts{SkipTempDirCreation}
      66        
1073             and defined $opts{SkipTempDirCreation}
1074             and $opts{SkipTempDirCreation}) {
1075             # Ensure that $user_temp_dir can be accessed by my drop priv'd child.
1076             # And only try to change perms to 0755 only if perms are not 0755
1077             # already.
1078 119         1372250 my $st = stat(cwd());
1079 119 50       57958 unless ((S_IMODE($st->mode) & 0755) >= 0755) {
1080 119 50       1543322 chmod 0755, cwd() or die <
1081             App-Fetchware-Util: Fetchware failed to change the permissions of the current
1082 0         0 temporary directory [@{[cwd()]} to 0755. The OS error was [$!].
1083             EOD
1084             }
1085             # Create a new tempdir for the droped prive user to use, and be sure
1086             # to chown it so they can actually write to it as well.
1087             # $new_temp_dir does not have a semaphore file, but its parent
1088             # directory does, which will still keep fetchware clean from
1089             # deleting this directory out from underneath us.
1090             #
1091             # Also note, that cwd() is "blindly" coded here, which makes it a
1092             # "dependency," but drop_privs() is meant to be called after start()
1093             # by fetchware::cmd_*(). It's not meant to be a generic subroutine
1094             # to drop privs, and it's also not really meant to be used by
1095             # fetchware extensions mostly just fetchware itself. Perhaps I
1096             # should move it back to bin/fetchware???
1097 119         1493337 my $new_temp_dir = tempdir("fetchware-$$-XXXXXXXXXX",
1098             DIR => cwd(), CLEANUP => 1);
1099             # Determine /etc/passwd entry for the "effective" uid of the
1100             # current fetchware process. I should use the "effective" uid
1101             # instead of the "real" uid, because effective uid is used to
1102             # determine what each uid can do, and the real uid is only
1103             # really used to track who the original user was in a setuid
1104             # program.
1105 119   50     662289 my ($name, $useless, $uid, $gid, $quota, $comment, $gcos, $dir,
1106             $shell, $expire)
1107             = getpwnam(config('user') // 'nobody');
1108 119 50       5147 chown($uid, $gid, $new_temp_dir) or die <
1109             App-Fetchware-Util: Fetchware failed to chown [$new_temp_dir] to the user it is
1110             dropping privileges to. This just shouldn't happen, and might be a bug, or
1111             perhaps your system temporary directory is full. The OS error was [$!].
1112             EOD
1113 119 50       4477 chmod(0700, $new_temp_dir) or die <
1114             App-Fetchware-Util: Fetchware failed to change the permissions of its new
1115             temporary directory [$new_temp_dir] to 0700 that it created, because its
1116             dropping privileges. This just shouldn't happen, and is bug, or perhaps your
1117             system temporary directory is full. The OS error is [$!].
1118             EOD
1119             # And of course chdir() to $new_temp_dir, because everything assumes
1120             # that the cwd() is where everything should be saved and done.
1121 119 50       5960 chdir($new_temp_dir) or die <
1122             App-Fetchware-Util: Fetchware failed to chdir() to its new temporary directory
1123             [$new_temp_dir]. This shouldn't happen, and is most likely a bug, or perhaps
1124             your system temporary directory is full. The OS error was [$!].
1125             EOD
1126             }
1127              
1128             # Open a pipe to allow the child to talk back to the parent.
1129 125 50       5113 pipe(READONLY, WRITEONLY) or die <
1130             App-Fetchware-Util: Fetchware failed to create a pipe to allow the forked
1131             process to communication back to the parent process. OS error [$!].
1132             EOD
1133             # Turn them into proper lexical file handles.
1134 125         3150 my ($readonly, $writeonly) = (*READONLY, *WRITEONLY);
1135              
1136             # Set up a SIGPIPE handler in case the writer closes the pipe before the
1137             # reader closes their pipe.
1138             $SIG{'PIPE'} = sub {
1139 0     0   0 die <
1140             App-Fetchware-Util: Fetchware received a PIPE signal from the OS indicating the
1141             pipe is dead. This should not happen, and is because the child was killed out
1142             from under the parent, or there is a bug. This is a fatal error, because it's
1143             possible the parent needs whatever information the child was going to use the
1144             pipe to send to the parent, and now it is unclear if the proper expected output
1145             has been received or not; therefore, we're just playing it safe and die()ing.
1146             EOD
1147 125         5864 };
1148            
1149             # Code below based on a cool forking idiom by Aristotle.
1150             # (http://blogs.perl.org/users/aristotle/2012/10/concise-fork-idiom.html)
1151 125         439859 for ( scalar fork ) {
1152             # Fork failed.
1153             # defined() operates on default variable, $_.
1154 125 50       16950 if (not defined $_) {
1155 0         0 die <
1156             App-Fetchware-Util: Fork failed! This shouldn't happen!?! Os error [$!].
1157             EOD
1158             }
1159              
1160             # Fork succeeded, Parent code goes here.
1161 125         856 my $kidpid = $_;
1162 125 50       4371 if ( $kidpid ) {
1163 125 50       31421 close $writeonly or die <
1164             App-Fetchware-Util: Failed to close $writeonly pipe in parent. Os error [$!].
1165             EOD
1166 125         997 my $output;
1167              
1168             # Read the child's output until child closes pipe sending EOF.
1169 125         17163779 $output .= $_ while (<$readonly>);
1170              
1171             # Close $readonly pipe, because we have received the output from
1172             # the user.
1173 125 50       5158 close $readonly or die <
1174             App-Fetchware-Util: Failed to close $readonly pipe in parent. Os error [$!].
1175             EOD
1176              
1177             # Just block waiting for the child to finish.
1178 125         590443 waitpid($kidpid, 0);
1179              
1180             # If the child failed ($? >> 8 != 0), then the parent should
1181             # fail as well, because the child only exists to drop privs with
1182             # the ability to still at a later time execute something as root
1183             # again, so the fork is needed, because once you drop privs
1184             # you can't get them back, and you don't want to be able to for
1185             # security reasons.
1186 125 50       3061 if (($? >> 8) != 0) {
1187             # Note this message is only vmsg()'d instead of die()'d,
1188             # because if its printed always, it could confuse users.
1189             # Because priv_drop()ing is the default, this error would be
1190             # seen all the time making getting confused by it likely.
1191 0         0 vmsg <
1192             App-Fetchware-Util: An error occured forcing fetchware to exit while fetchware
1193             has forked to drop its root priviledges to avoid downloading files and building
1194             programs as root. Root priviledges are only maintained to install the software
1195             in a system directory requiring root access. This error should have been
1196             previously printed out by fetchware's lower priviledged child process above.
1197             EOD
1198             # Exit non-zero indicating failure, because whatever the
1199             # child did failed, and the child's main eval {} in
1200             # bin/fetchware caught that failure, printed it to the
1201             # screen, and exit()ed non-zero for failure. And since the
1202             # child failed ($? >> 8 != 0), the parent should fail too.
1203 0         0 exit 1;
1204             # If successful, return to the child a ref of @output to caller.
1205             } else {
1206 125         9592 return \$output;
1207             }
1208             # Fork succeeded, child code goes here.
1209             } else {
1210 0 0       0 close $readonly or die <
1211             App-Fetchware-Util: Failed to close $readonly pipe in child. Os error [$!].
1212             EOD
1213             # Drop privs.
1214             # drop_privileges() dies on an error just let drop_privs() caller
1215             # catch it.
1216 0         0 my ($uid, $gid) = drop_privileges($regular_user);
1217              
1218              
1219             # Execute the coderef that is supposed to be done as non-root.
1220 0         0 $child_code->($writeonly);
1221              
1222             # Now close the pipe, to avoid creating a dead pipe causing a
1223             # SIGPIPE to be sent to the parent.
1224 0 0       0 close $writeonly or die <
1225             App-Fetchware-Util: Failed to close $writeonly pipe in child. Os error [$!].
1226             EOD
1227              
1228             # Exit success, because failure is only indicated by a thrown
1229             # exception that bin/fetchware's main eval {} will catch, print,
1230             # and exit non-zero indicating failure.
1231             # Use POSIX's _exit() to avoid calling END{} blocks. This *must*
1232             # be done to prevent File::Temp's END{} block from attempting to
1233             # delete the temp directory that the parent still needs to
1234             # finish installing or uninstalling. The parent's END{} block's
1235             # will still be called, so this just turns off the child
1236             # deleting the temp dir not the parent.
1237 0         0 _exit 0;
1238             }
1239             }
1240             # Non-Unix OSes just execute the $child_code.
1241             } else {
1242 0         0 return $dont_drop_privs->($child_code);
1243             }
1244             }
1245              
1246              
1247              
1248              
1249             ###BUGALERT### Add quotemeta() support to pipe parsers to help prevent attacks.
1250              
1251              
1252              
1253             { # Bareblock just for the $MAGIC_NUMBER.
1254             # Determine $front_magic
1255             my $front_magic;
1256             $front_magic = int(rand(8128389023));
1257             # For no particular reason convert the random integer into hex, because I
1258             # never store something in decimal and then exact same thing in hex.
1259             $front_magic = $front_magic . sprintf("%x", $front_magic);
1260             # Run srand() again to change random number generator between rand() calls.
1261             # Not really necessary, but should make it harder to guess correct magic
1262             # numbers.
1263             srand(time());
1264             # Same a $front_magic.
1265             my $back_magic = int(rand(986487516));
1266             # Octal this time :) for no real reason.
1267             $back_magic = $back_magic . sprintf("%o", $back_magic);
1268             my $MAGIC_NUMBER = $front_magic
1269             . 'MAGIC_NUMBER_REPLACING_NEWLINE'
1270             . $back_magic;
1271              
1272             sub write_dropprivs_pipe {
1273 1     1 1 11928 my $write_pipe = shift;
1274              
1275 1         55 for my $a_var (@_) {
1276 4 50       270 die <
1277             fetchware: Huh? [$a_var] has fetchware's MAGIC_NUMBER in it? This shouldn't
1278             happen, and messes up fetchware's simple IPC. You should never see this error,
1279             because it's not a particuarly magic number if anybody actually uses it. This is
1280             most likely a bug, so please report it.
1281             EOD
1282              
1283             # Write to the $write_pipe, but use the $MAGIC_NUMBER instead of just
1284             # newline.
1285 4         117 print $write_pipe $a_var . $MAGIC_NUMBER;
1286             }
1287             }
1288              
1289              
1290              
1291             sub read_dropprivs_pipe {
1292 120     120 1 30627 my $output = shift;
1293              
1294 120 50       1392 die <
1295             App-Fetchware-Util: pipe_read_newling() was called with an output variable
1296             [$output] that was not a scalar reference. It must be a scalar reference.
1297             EOD
1298              
1299 120         327 my @variables;
1300 120         3373 for my $variable (split(/$MAGIC_NUMBER/, $$output)) {
1301             # And some error handling just in case.
1302 111 50       525 die <
1303             fetchware: Huh? The child failed to write the proper variable back to the
1304             parent! The variable is [$variable]. This should be defined but it is
1305             not!
1306             EOD
1307             # Clear possibly tainted variables. It's a weird bug that makes no
1308             # sense. I don't turn -t or -T on, so what gives??? If you're curious
1309             # try commenting out the taint clearing code below, and running the
1310             # t/bin-fetchware-install.t test file (Or any other ones that call
1311             # drop_privs().).
1312 111         282 my $untainted;
1313             # Need the m//ms options to match strings with newlines in them.
1314 111 50       2665 if ($variable =~ /(.*)/ms) {
1315 111         1445 $untainted = $1;
1316             } else {
1317 0         0 die <
1318             App::Fetchware::Util: Untaint failed! Huh! This just shouldn't happen! It's
1319             probably a bug.
1320             EOD
1321             }
1322              
1323             # Push $untainted instead of just $variable, because I want to return
1324             # untatined data instead of potentially tainted data.
1325 111         1049 push @variables, $untainted;
1326             }
1327              
1328 120         1233 return @variables;
1329             }
1330             ###BUGALERT### Add some pipe parsers that use Storable too.
1331              
1332             } # End $MAGIC_NUMBER bare block.
1333              
1334              
1335              
1336              
1337              
1338              
1339              
1340             sub do_nothing {
1341 3     3 1 28 return;
1342             }
1343              
1344              
1345              
1346              
1347              
1348              
1349             { # Begin scope block for $original_cwd.
1350              
1351             # $original_cwd is a scalar variable that stores fetchware's original
1352             # working directory for later use if its needed. It is access with
1353             # original_cwd() below.
1354             my $original_cwd;
1355             # $fh_sem is a semaphore lock file that create_tempdir() creates, and
1356             # cleanup_tempdir() closes clearing the lock. This is used to support
1357             # fetchware clean. The filehandle needs to be declared outside
1358             # create_tempdir()'s scope, because when this filehandle goes out of scope
1359             # the file is closed, and the lock is released, but fetchware needs to keep
1360             # hold of this lock for the life of fetchware to ensure that any fetchware
1361             # clean won't delete this fetchware temporary directory.
1362             my $fh_sem;
1363              
1364              
1365             ###BUGALERT### Add support for the -f/--force option to force deleting fetchware
1366             #temp dirs even if locked.
1367             sub create_tempdir {
1368 343     343 1 61265 my %opts = @_;
1369              
1370 343         1551 msg 'Creating temp dir to use to install your package.';
1371              
1372             # Ask for better security.
1373 343         5411 File::Temp->safe_level( File::Temp::HIGH );
1374              
1375             # Create the temp dir in the portable locations as returned by
1376             # File::Spec->tempdir() using the specified template (the weird $$ is this
1377             # processes process id), and cleaning up at program exit.
1378 343         11545 my $exception;
1379             my $temp_dir;
1380 343 100       1110 eval {
1381 343         687 local $@;
1382              
1383             # Determine tempdir()'s arguments.
1384 343         2869 my @args = ("fetchware-$$-XXXXXXXXXX");#, TMPDIR => 1);
1385              
1386             # Specify the caller's TempDir (DIR) if they specify it.
1387 343 100       3628 push @args, DIR => $opts{TempDir} if defined $opts{TempDir};
1388              
1389             # Specify either system temp directory or user specified directory.
1390 343 100       2749 push @args,
1391             (defined $opts{TempDir} ? (DIR => $opts{TempDir}) : (TMPDIR => 1));
1392              
1393             # Don't CLEANUP if KeepTempDir is set.
1394 343 100       1596 push @args, CLEANUP => 1 if not defined $opts{KeepTempDir};
1395              
1396             # Call tempdir() with the @args I've built.
1397 343         3006 $temp_dir = tempdir(@args);
1398              
1399             # Only when we do *not* drop privs...
1400 341 50 33     893589 if (config('stay_root')
      33        
1401             or ($< != 0 or $> != 0)
1402             ) {
1403             # ...Must chmod 700 so gpg's localized keyfiles are good.
1404 0 0       0 chmod(0700, $temp_dir) or die <
1405             App-Fetchware-Util: Fetchware failed to change the permissions of its temporary
1406             directory [$temp_dir] to 0700. This should not happen, and is a bug, or perhaps
1407             your system's temporary directory is full. The OS error was [$!].
1408             EOD
1409             }
1410              
1411 341         744 $exception = $@;
1412 341         1603 1; # return true unless an exception is thrown.
1413             } or die <
1414             App-Fetchware: run-time error. Fetchware tried to use File::Temp's tempdir()
1415             subroutine to create a temporary file, but tempdir() threw an exception. That
1416             exception was [$exception]. See perldoc App::Fetchware.
1417             EOD
1418              
1419 341         3896612 $original_cwd = cwd();
1420 341         41691 vmsg "Saving original working directory as [$original_cwd]";
1421              
1422             # Change directory to $CONFIG{TempDir} to make unarchiving and building happen
1423             # in a temporary directory, and to allow for multiple concurrent fetchware
1424             # runs at the same time.
1425 341 50       20719 chdir $temp_dir or die <
1426             App-Fetchware: run-time error. Fetchware failed to change its directory to the
1427             temporary directory that it successfully created. This just shouldn't happen,
1428             and is weird, and may be a bug. See perldoc App::Fetchware.
1429             EOD
1430 341         3923 vmsg "Successfully changed working directory to [$temp_dir].";
1431              
1432             # Create 'fetcwhare.sem' - the fetchware semaphore lock file.
1433 341 50       1135156 open $fh_sem, '>', 'fetchware.sem' or die <
1434             App-Fetchware-Util: Failed to create [fetchware.sem] semaphore lock file! This
1435             should not happen, because fetchware is creating this file in a brand new
1436             directory that only fetchware should be accessing. You simply shouldn't see this
1437             error unless some one is messing with fetchware, or perphaps there actually is a
1438             bug? I don't know, but this just shouldn't happen. It's so hard to trigger it to
1439             happen, it can't easily be tested in fetchware's test suite. OS error [$!].
1440             EOD
1441 341         1882 vmsg "Successfully created [fetchware.sem] semaphore lock file.";
1442             # Now flock 'fetchware.sem.' This should
1443             # Use LOCK_NB so flock won't stupidly wait forever and ever until the lock
1444             # becomes available.
1445 341 50       4837 flock $fh_sem, LOCK_EX | LOCK_NB or die <
1446             App-Fetchware-Util: Failed to flock [fetchware.sem] semaphore lock file! This
1447             should not happen, because this is being done in a brand new temporary directory
1448             that only this instance of fetchware cares about. This just shouldn't happen. OS
1449             error [$!].
1450             EOD
1451 341         1697 vmsg "Successfully locked [fetchware.sem] semaphore lock file using flock.";
1452              
1453 341         6452 msg "Temporary directory created [$temp_dir]";
1454              
1455 341         15680 return $temp_dir;
1456             }
1457              
1458              
1459              
1460             sub original_cwd {
1461 993     993 1 39510 return $original_cwd;
1462             }
1463              
1464              
1465              
1466             sub cleanup_tempdir {
1467 327     327 1 6978 msg 'Cleaning up temporary directory temporary directory.';
1468              
1469             # Close and unlock the fetchware semaphore lock file, 'fetchware.sem.'
1470 327 50       1921 if (defined $fh_sem) {
1471 327 50       10623 close $fh_sem or die <
1472             App-Fetchware-Util: Huh? close() failed! Fetchware failed to close(\$fh_sem).
1473             Perhaps some one or something deleted it under us? Maybe a fetchware clean was
1474             run with the force flag (--force) while this other fetchware was running?
1475             OS error [$!].
1476             EOD
1477 327         1835 vmsg <
1478             Closed [fetchware.sem] filehandle to unlock this fetchware temporary directory from any
1479             fetchware clean runs.
1480             EOM
1481             }
1482              
1483             # chdir to original_cwd() directory, so File::Temp can delete the tempdir.
1484             # This is necessary, because operating systems do not allow you to delete a
1485             # directory that a running program has as its cwd.
1486 327 50       1803 if (defined(original_cwd())) {
1487 327         981 vmsg "Changing directory to [@{[original_cwd()]}].";
  327         1067  
1488 327 50       1921 chdir(original_cwd()) or die <
1489             App-Fetchware: run-time error. Fetchware failed to chdir() to
1490 0         0 [@{[original_cwd()]}]. See perldoc App::Fetchware.
1491             EOD
1492             }
1493              
1494             # cleanup_tempdir() used to actually delete the temporary directory by using
1495             # File::Temp's cleanup() subroutine, but that subroutine deletes *all*
1496             # temporary directories that File::Temp has created and marked for deletion,
1497             # which might include directories created before this call to
1498             # cleanup_tempdir(), but are needed after. Therefore, cleanup_tempdir() no
1499             # longer actually deletes anything; instead, File::Temp can do it in its END
1500             # handler.
1501             #
1502             # The code below is left here on purpose, to remind everyone *not* to call
1503             # File::Temp's cleanup() here!! Do not do it!
1504             ###DONOTDO#### Call File::Temp's cleanup subrouttine to delete fetchware's temp
1505             ###DONOTDO#### directory.
1506             ###DONOTDO###vmsg 'Cleaning up temporary directory.';
1507             ###DONOTDO###File::Temp::cleanup();
1508              
1509 327         1200 vmsg "Leaving tempdir alone. File::Temp's END handler will delete it.";
1510              
1511 327         1622 vmsg 'Clearing internal %CONFIG variable that hold your parsed Fetchwarefile.';
1512 327         4871 __clear_CONFIG();
1513              
1514 327         1129 msg 'Cleaned up temporary directory.';
1515              
1516             # Return true.
1517 327         1305 return 'Cleaned up tempdir';
1518             }
1519              
1520              
1521             } # End scope block for $original_cwd and $fh_sem.
1522              
1523             1;
1524              
1525             =pod
1526              
1527             =head1 NAME
1528              
1529             App::Fetchware::Util - Miscelaneous functions for App::Fetchware.
1530              
1531             =head1 VERSION
1532              
1533             version 1.014
1534              
1535             =head1 SYNOPSIS
1536              
1537             use App::Fetchware::Util ':UTIL';
1538              
1539              
1540             # Logging subroutines.
1541             msg 'message to print to STDOUT';
1542              
1543             vmsg 'message to print to STDOUT';
1544              
1545              
1546             # Run external command subroutine.
1547             run_prog($program, @args);
1548              
1549              
1550             # Download subroutines.
1551             my $dir_list = download_dirlist($ftp_or_http_url)
1552              
1553             my $dir_list = ftp_download_dirlist($ftp_url);
1554              
1555             my $dir_list = http_download_dirlist($http_url);
1556              
1557              
1558             my $filename = download_file($url)
1559              
1560             my $filename = download_ftp_url($url);
1561              
1562             my $filename = download_http_url($url);
1563              
1564             my $filename = download_file_url($url);
1565              
1566              
1567             # Miscelaneous subroutines.
1568             just_filename()
1569              
1570             do_nothing();
1571              
1572              
1573             # Temporary directory subroutines.
1574             my $temp_dir = create_tempdir();
1575              
1576             my $original_cwd = original_cwd();
1577              
1578             cleanup_tempdir();
1579              
1580             =head1 DESCRIPTION
1581              
1582             App::Fetchware::Util holds miscelaneous utilities that fetchware needs for
1583             various purposes such as logging and controling executed processes based on -q
1584             or -v switches (msg(), vmsg(), run_prog()), subroutines for downloading
1585             directory listings (*_dirlist()) or files (download_*()) using ftp, http, or
1586             local files (file://), do_nothing() for extensions to fetchware, and subroutines
1587             for managing a temporary directory.
1588              
1589             =head1 LOGGING SUBROUTINES
1590              
1591             These subroutines' log messages generated by fetchware by printing them to
1592             C. They do not currently support logging to a file directly, but you
1593             could redirect fetchware's standard output to a file using your shell if you
1594             want to:
1595              
1596             fetchware any arguments > fetchware.log
1597             fetchware upgrade-all > fetchware.log
1598              
1599             =head2 Standards for using msg() and vmsg()
1600              
1601             msg() should be used to describe the main events that happen, while vmsg()
1602             should be used to describe what all of the main subroutine calls do.
1603              
1604             For example, cmd_uninstall() has a msg() at the beginning and at the end, and so
1605             do the main App::Fetchware subroutines that it uses such as start(), download(),
1606             unarchive(), end() and so on. They both use vmsg() to add more detailed messages
1607             about the particular even "internal" things they do.
1608              
1609             msg() and vmsg() are also used without parens due to their appropriate
1610             prototypes. This makes them stand out from regular old subroutine calls more.
1611              
1612             =head2 msg()
1613              
1614             msg 'message to print to STDOUT' ;
1615             msg('message to print to STDOUT');
1616              
1617             msg() simply takes a list of scalars, and it prints them to STDOUT according to
1618             any verbose (-v), or quiet (-q) options that the user may have provided to
1619             fetchware.
1620              
1621             msg() will still print its arguments if the user provided a -v (verbose)
1622             argument, but it will B print its argument if the user provided a -q (quiet)
1623             command line option.
1624              
1625             =over
1626             =item This subroutine makes use of prototypes, so that you can avoid using parentheses around its args to make it stand out more in code.
1627              
1628             =back
1629              
1630             =head2 vmsg()
1631              
1632             vmsg 'message to print to STDOUT' ;
1633             vmsg('message to print to STDOUT');
1634              
1635             vmsg() simply takes a list of scalars, and it prints them to STDOUT according to
1636             any verbose (-v), or quiet (-q) options that the user may have provided to
1637             fetchware.
1638              
1639             vmsg() will B print its arguments if the user provided a -v (verbose)
1640             argument, but it will B print its argument if the user provided a -q (quiet)
1641             command line option.
1642              
1643             =over
1644             =item This subroutine makes use of prototypes, so that you can avoid using parentheses around its args to make it stand out more in code.
1645              
1646             =back
1647              
1648             =head1 EXTERNAL COMMAND SUBROUTINES
1649              
1650             run_prog() should be the B function you use to execute external commands
1651             when you L, or L,
1652             because run_prog() properly checks if the user specified the quiet switch
1653             (C<-q>), and disables external commands from printing to C if it has
1654             been enabled.
1655              
1656             =head2 run_prog()
1657              
1658             run_prog($program, @args);
1659              
1660             # Or let run_prog() deal with splitting the $command into multiple pieces.
1661             run_prig($command);
1662              
1663             run_prog() uses L to execute the program for you. Only the secure way of
1664             avoiding the shell is used, so you can not use any shell redirection or any
1665             shell builtins.
1666              
1667             If the user ran fetchware with -v (verbose) then run_prog() changes none of its
1668             behavior it still just executes the program. However, if the user runs the
1669             program with -q (quiet) specified, then the the command is run using a piped
1670             open to capture the output of the program. This captured output is then ignored,
1671             because the user asked to never be bothered with the output. This piped open
1672             uses the safer shell avoiding syntax on systems with L, and systems
1673             without L, Windows, the older less safe syntax is used. Backticks are
1674             avoided, because they always use the shell.
1675              
1676             run_prog() when called with only one argument will split that one argument into
1677             multiple pieces using L quotewords() subroutine, which
1678             properly deals with quotes just like the shell does. quotewords() is always used
1679             even if you provide an already split up list of arguments to run_prog().
1680              
1681             =head2 Executing external commands without using run_prog()
1682              
1683             msg(), vmsg(), and run_prog() determine if -v and if -q were specified by
1684             checking the values of the global variables listed below:
1685              
1686             =over
1687              
1688             =item * $fetchware::quiet - is C<0> if -q was B specified.
1689              
1690             =item * $fetchware::verbose - is C<0> if -v was B specified.
1691              
1692             =back
1693              
1694             Both of these variables work the same way. If they are 0, then -q or -v was
1695             B specified. And if they are defined and greather than (>) 0, then -q or -v
1696             were specified on the command line. You should test for greater than 0 B
1697             B<== 1>, because Fetchware takes advantage of a cool feature in GetOpt::Long
1698             allowing the user to specify -v and -q more than once. This triggers either
1699             $fetchware::quiet or $fetchware::verbose to be greater than one, which would
1700             cause a direct C<== 1> test to fail even though the user is no asking for
1701             I verbose messages. Internally Fetchware only supports one verbositly
1702             level.
1703              
1704             =head1 DOWNLOAD SUBROUTINES
1705              
1706             App::Fetchware::Util's download_*() and *_dirlist() subroutines allow you to
1707             download FTP, HTTP, or local file (file://) directory listings or files
1708             respectively.
1709              
1710             =over
1711             =item NOTICE
1712             Each *_dirlist() subroutine returns its own format that is different from the
1713             others. Fetchware uses the *_parse_filelist() subroutines to parse this
1714             differing directory listings into a specifc format of an array of arrays of
1715             filenames and timestamps. You could load these subroutines from the
1716             C App::Fetchware export tag to use in your Fetchwarefile or
1717             your fetchware extension.
1718              
1719             =back
1720              
1721             =head2 download_dirlist()
1722              
1723             my $dir_list = download_dirlist($url)
1724              
1725             my $dir_list = download_dirlist(PATH => $path)
1726              
1727             Can be called with either a $url or a PATH parameter. When called with a $url
1728             parameter, the specified $url is downloaded using no_mirror_download_dirlist(),
1729             and returned if successful. If it fails then each C the user specified
1730             is also tried unitl there are no more mirrors, and then an exception is thrown.
1731              
1732             If you specify a PATH parameter instead of a $url parameter, then that path is
1733             appended to each C, and the resultant url is downloaded using
1734             no_mirror_download_dirlist().
1735              
1736             =head2 no_mirror_download_dirlist()
1737              
1738             my $dir_list = no_mirror_download_dirlist($ftp_or_http_url)
1739              
1740             Downloads a ftp or http url and assumes that it will be downloading a directory
1741             listing instead of an actual file. To download an actual file use
1742             L. download_dirlist returns the directory listing that it
1743             obtained from the ftp or http server. ftp server will be an arrayref of C
1744             like output, while the http output will be a scalar of the HTML dirlisting
1745             provided by the http server.
1746              
1747             =head2 ftp_download_dirlist()
1748              
1749             my $dir_list = ftp_download_dirlist($ftp_url);
1750              
1751             Uses Net::Ftp's dir() method to obtain a I directory listing. lookup()
1752             needs it in I format, so that the timestamp algorithm has access to each
1753             file's timestamp.
1754              
1755             Returns an array ref of the directory listing.
1756              
1757             =head2 http_download_dirlist()
1758              
1759             my $dir_list = http_download_dirlist($http_url);
1760              
1761             Uses HTTP::Tiny to download a HTML directory listing from a HTTP Web server.
1762              
1763             Returns an scalar of the HTML ladden directory listing.
1764              
1765             If an even number of other options are specified (a faux hash), then those
1766             options are forwarded on to L's new() method. See L for
1767             details about what these options are. For example, you couse use this to add a
1768             C header to your request if a download site annoying checks referrers.
1769              
1770             =head2 file_download_dirlist()
1771              
1772             my $file_listing = file_download_dirlist($local_lookup_url)
1773              
1774             Glob's provided $local_lookup_url, and builds a directory listing of all files
1775             in the provided directory.
1776              
1777             =head2 download_file()
1778              
1779             my $filename = download_file($url)
1780              
1781             my $filename = download_file(PATH => $path)
1782              
1783             Can be called with either a $url or a PATH parameter. When called with a $url
1784             parameter, the specified $url is downloaded using no_mirror_download_file(),
1785             and returned if successful. If it fails then each C the user specified
1786             is also tried unitl there are no more mirrors, and then an exception is thrown.
1787              
1788             If you specify a PATH parameter instead of a $url parameter, then that path is
1789             appended to each C, and the resultant url is downloaded using
1790             no_mirror_download_file().
1791              
1792             =head2 no_mirror_download_file()
1793              
1794             my $filename = no_mirror_download_file($url)
1795              
1796             Downloads one $url and assumes it is a file that will be downloaded instead of a
1797             file listing that will be returned. no_mirror_download_file() returns the file
1798             name of the file it downloads.
1799              
1800             Like its name says it does not try any configured mirrors at all. This
1801             subroutine should not be used; instead download_file() should be used, because
1802             you should respect your user's desired mirrors.
1803              
1804             =head2 download_ftp_url()
1805              
1806             my $filename = download_ftp_url($url);
1807              
1808             Uses Net::FTP to download the specified FTP URL using binary mode.
1809              
1810             =head2 download_http_url()
1811              
1812             my $filename = download_http_url($url);
1813              
1814             Uses HTTP::Tiny to download the specified HTTP URL.
1815              
1816             Supports adding extra arguments to HTTP::Tiny's new() constructor. These
1817             arguments are B checked for correctness; instead, they are simply forwarded
1818             to HTTP::Tiny, which does not check them for correctness either. HTTP::Tiny
1819             simply loops over its internal listing of what is arguments should be, and then
1820             accesses the arguments if they exist.
1821              
1822             This was really only implemented to allow App::FetchwareX::HTMLPageSync to change
1823             its user agent string to avoid being blocked or freaking out Web developers that
1824             they're being screen scraped by some obnoxious bot as HTMLPageSync is wimpy and
1825             harmless, and only downloads one page.
1826              
1827             You would add an argument like this:
1828             download_http_url($http_url, agent => 'Firefox');
1829              
1830             See HTTP::Tiny's documentation for what these options are.
1831              
1832             =head2 download_file_url()
1833              
1834             my $filename = download_file_url($url);
1835              
1836             Uses File::Copy to copy ("download") the local file to the current working
1837             directory.
1838              
1839             =head1 TEMPDIR SUBROUTINES
1840              
1841             These subroutines manage the creation of a temporary directory for you. They
1842             also implement the original_cwd() getter subroutine that returns the current
1843             working directory fetchware was at before create_tempdir() chdir()'d to the
1844             temporary directory you specify. File::Temp's tempdir() is used, and
1845             cleanup_tempdir() manages the C fetchware semaphore file.
1846              
1847             =over
1848             =item NOTICE
1849             App::Fetchware::Util's temporary directory creation utilities, create_tempdir(),
1850             original_cwd(), and cleanup_tempdir(), only keep track of one tempdir at a time. If
1851             you create another tempdir with create_tempdir() it will override the value of
1852             original_cwd(), which may mess up other functions that call create_tempdir(),
1853             original_cwd(), and cleanup_tempdir(). Therefore, becareful when you call these
1854             functions, and do B use them inside a fetchware extension if you reuse
1855             App::Fetchware's start() and end(), because App::Fetchware's start() and end()
1856             use these functions, so your use of them will conflict. If you still need to
1857             create a tempdir just call File::Temp's tempdir() directly.
1858              
1859             =back
1860              
1861             =head2 create_tempdir()
1862              
1863             my $temp_dir = create_tempdir();
1864              
1865             Creates a temporary directory, chmod 700's it, and chdir()'s into it.
1866              
1867             Accepts the fake hash argument C 1>, which tells create_tempdir()
1868             to B delete the temporary directory when the program exits.
1869              
1870             Also, accepts C '/tmp'> to specify what temporary directory to
1871             use. The default with out this argument is to use tempdir()'s default, which is
1872             whatever File::Spec's tmpdir() says to use.
1873              
1874             The C 1> option causes create_tempdir() to B chown to
1875             config('user').
1876              
1877             =head3 Locking Fetchware's temp directories with a semaphore file.
1878              
1879             In order to support C, create_tempdir() creates a semaphore
1880             file. The file is used by C (via bin/fetchware's cmd_clean())
1881             to determine if another fetchware process out there is currently using this
1882             temporary directory, and if it is not, the file is not currently locked with
1883             flock, then the entire directory is deleted using File::Path's remove_path()
1884             function. If the file is there and locked, then the directory is skipped by
1885             cmd_clean().
1886              
1887             cleanup_tempdir() is responsible for unlocking the semaphore file that
1888             create_tempdir() creates. However, the coolest part of using flock is that if
1889             fetchware is killed in any manner whether its C block or File::Temp's
1890             Cblock run, the OS will still unlock the file, so no edge cases need
1891             handling, because the OS will do them for us!
1892              
1893             =head2 original_cwd()
1894              
1895             my $original_cwd = original_cwd();
1896              
1897             original_cwd() simply returns the value of fetchware's $original_cwd that is
1898             saved inside each create_tempdir() call. A new call to create_tempdir() will
1899             reset this value. Note: App::Fetchware's start() also calls create_tempdir(), so
1900             another call to start() will also reset original_cwd().
1901              
1902             =head2 cleanup_tempdir()
1903              
1904             cleanup_tempdir();
1905              
1906             Cleans up B temporary files or directories that anything in this process used
1907             File::Temp to create. You cannot only clean up one directory or another;
1908             instead, you must just use this sparingly or in an END block although file::Temp
1909             takes care of that for you unless you asked it not to.
1910              
1911             It also closes $fh_sem, which is the filehandle of the 'fetchware.sem' file
1912             create_tempdir() opens and I. By closing it in cleanup_tempdir(), we're
1913             unlocking it. According to MJD's "File Locking Tips and Traps," it's better to
1914             just close the file, then use flock to unlock it.
1915              
1916             =head1 SECURITY SUBROUTINES
1917              
1918             This section describes Utilty subroutines that can be used for checking security
1919             of files on the file system to see if fetchware should open and use them.
1920              
1921             =head2 safe_open()
1922              
1923             my $fh = safe_open($file_to_check, <
1924             App-Fetchware-Extension???: Failed to open file [$file_to_check]! Because of
1925             OS error [$!].
1926             EOE
1927              
1928             # To open for writing instead of reading
1929             my $fh = safe_open($file_to_check, < '>');
1930             App-Fetchware-Extension???: Failed to open file [$file_to_check]! Because of
1931             OS error [$!].
1932             EOE
1933              
1934             safe_open() takes $file_to_check and does a bunch of file checks on that
1935             file to determine if it's safe to open and use the contents of that file in
1936             your program. Instead of returning true or false, it returns a file handle of
1937             the file you want to check that has already been open for you. This is done to
1938             prevent race conditions between the time safe_open() checks the file's safety
1939             and the time the caller actually opens the file.
1940              
1941             safe_open() also takes an optional second argument that specifies a caller
1942             specific error message that replaces the generic default one.
1943              
1944             Fetchware occasionally needs to write files especially in fetchware's new()
1945             command; therefore safe_open() also takes the fake hash argument
1946             C 'E'>, which opens the file in a mode specified by the caller.
1947             C<'E'> is for writing for example. See C for a list of
1948             possible modes.
1949              
1950             In fetchware, this subroutine is used to check if every file fetchware
1951             opens is safe to do so. It is based on is_safe() and is_very_safe() from the
1952             Perl Cookbook by Tom Christiansen and Nathan Torkington.
1953              
1954             What this subroutine checks:
1955              
1956             =over
1957              
1958             =item *
1959              
1960             It opens the file you give to it as an argument, and all subsequent operations
1961             are done on the opened filehandle to prevent race conditions.
1962              
1963             =item *
1964              
1965             Then it checks that the owner of the specified file must be either the superuser
1966             or the user who ran fetchware.
1967              
1968             =item *
1969              
1970             It checks that the mode, as returned by File::stat's overridden stat, is not
1971             writable by group or other. Fancy MAC permissions such as Linux's extfs's
1972             extensions and fancy Windows permissions are B currently checked.
1973              
1974             =item *
1975              
1976             Then safe_open() stat's each and every parent directory that is in this file's
1977             full path, and runs the same checks that are run above on each parent directory.
1978              
1979             =item *
1980              
1981             _PC_CHOWN_RESTRICTED is not tested; instead what is_very_safe() does is simply
1982             always done. Because even with A _PC_CHOWN_RESTRICTED test, /home, for example,
1983             could be 777. This is Unix after all, and root can do anything including screw
1984             up permissions on system directories.
1985              
1986             =back
1987              
1988             If you actually are some sort of security expert, please feel free to
1989             double-check if the list of stuff to check for is complete, and perhaps even the
1990             Perl implementation to see if the subroutine really does check if
1991             safe_open($file_to_check) is actually safe.
1992              
1993             =over
1994              
1995             =item WARNING
1996              
1997             According to L's chmod() documentation, on Win32 perl's Unixish file
1998             permissions arn't supported only "owner" is:
1999              
2000             "Only good for changing "owner" read-write access, "group", and "other" bits are
2001             meaningless. (Win32)"
2002              
2003             I'm not completely sure this means that under Win32 only owner perms mean
2004             something, or if just chmod()ing group or ther bits don't do anything, but
2005             testing if group and other are rwx does work. This needs testing.
2006              
2007             And remember this only applies to Win32, and fetchware has not yet been properly
2008             ported or tested under Win32 yet.
2009              
2010             =back
2011              
2012             =head2 drop_privs()
2013              
2014             my $output = drop_privs(sub {
2015             my $write_pipe = shift;
2016             # Do stuff as $regular_user
2017             ...
2018             # Use write_dropprivs_pipe to share variables back to parent.
2019             write_dropprivs_pipe($write_pipe, $var1, $var2, ...);
2020              
2021             }, $regular_user
2022             );
2023              
2024             # Back in the parent, use read_dropprivs_pipe() to read in whatever
2025             # variables the child shared with us.
2026             my ($var1, $var2, ...) = read_dropprivs_pipe($output);
2027              
2028             Forks and drops privs to $regular_user, and then executes whatever is in the
2029             first argument, which should be a code reference. Throws an exception on any
2030             problems with the fork.
2031              
2032             It only allows you to specify what the lower priveledged user does. The parent
2033             process's behavior can not be changed. All the parent does:
2034              
2035             =over
2036              
2037             =item *
2038              
2039             Create a pipe to allow the child to communicate any information back to the
2040             parent.
2041              
2042             =item *
2043              
2044             Read any data the child may write to that pipe.
2045              
2046             =item *
2047              
2048             After the child has died, collect the child's exit status.
2049              
2050             =item *
2051              
2052             And return the output the child wrote on the pipe as a scalar reference.
2053              
2054             =back
2055              
2056             Whatever the child writes is returned. drop_privs() does not use Storable or
2057             JSON or XML or anything. It is up to you to specify how the data is to be
2058             represented and used. However, L and
2059             L are provided. They provide a simple way to store
2060             multiple variables that can have any character in them including newline. See
2061             their documentation for details.
2062              
2063             =over
2064              
2065             =item SECURITY NOTICE
2066              
2067             The output returned by drop_privs() is whatever the child wants it to be. If
2068             somehow the child got hacked, the $output could be something that could cause
2069             the parent (which has root perms!) to execute some code, or otherwise do
2070             something that could cause the child to gain root access. So be sure to check
2071             how you use drop_privs() return value, and definitley don't just string eval it.
2072             Structure it so the return value can only be used as data for variables, and
2073             that those variables are never executed by root.
2074              
2075             =back
2076              
2077             drop_privs() handles being on nonunix for you. On a platform that is not Unix
2078             that does not have Unix's fork() and exec() security model, drop_privs() simply
2079             executes the provided code reference I dropping priveledges.
2080              
2081             =over
2082              
2083             =item USABILITY NOTICE
2084              
2085             drop_privs()'s implementation depends on start() creating a tempdir and
2086             chdir()ing to it. Furthermore, drop_privs() sometimes creates a tempdir of its
2087             own, and it does not do a chdir back to another directory, so drop_privs()
2088             depends on end() to chdir back to original_cwd(). Therefore, do not use
2089             drop_privs() without also using start() and end() to manage a temporary
2090             directory for drop_privs().
2091              
2092             =back
2093              
2094             drop_privs() also supports a C 1> option that turns
2095             off drop_privs() creating a temporary diretory to give the child a writable
2096             temporary directory. This option is only used by cmd_new(), and probably only
2097             really needs to be used there. Also, note that you must provide this option
2098             after the $child_code coderef, and the $regular user options. Like so,
2099             C 1>.
2100              
2101             =head2 drop_privs() PIPE PARSING UTILITIES
2102              
2103             drop_privs() uses a pipe for IPC between the child and the parent. This section
2104             contains utilties that help users of drop_privs() parse the input and output
2105             they send from the child back to the parent.
2106              
2107             Use write_dropprivs_pipe() to send data back to the parent, that later you'll read
2108             with read_dropprivs_pipe() back in the parent.
2109              
2110             =head3 write_dropprivs_pipe()
2111              
2112             write_dropprivs_pipe($write_pipe, $variable1, $variable2, $variable3);
2113              
2114             Simply uses the caller provided $write_pipe file handle to write the rest of its
2115             args to that file handle separated by a I.
2116              
2117             This magic number is just generated uniquely each time App::Fetchware::Util is
2118             compiled. This number replaces using newline to separate each of the variables
2119             that write_dropprivs_pipe() writes. This way you can include newline, and in
2120             fact anything that does not contain the magic number, which is obviously
2121             suitably unlikely.
2122              
2123             =head3 read_dropprivs_pipe()
2124              
2125             my ($variable1, $variable2, $variable3) = pipe_read_newling($output);
2126              
2127             read_dropprivs_pipe() opens the scalar $output, and returns a list of $outputs
2128             parsed out variables split on the $MAGIC_NUMBER, which is randomly generated
2129             during each time you run Fetchware to avoid you every actually using it.
2130              
2131             =head1 MISCELANEOUS UTILTY SUBROUTINES
2132              
2133             This is just a catch all category for everything else in App::Fetchware::Utility.
2134              
2135             =head2 do_nothing()
2136              
2137             do_nothing();
2138              
2139             do_nothing() does nothing but return. It simply returns doing nothing. It is
2140             meant to be used by App::Fetchware "subclasses" that "override" App::Fetchware's
2141             API subroutines to make those API subroutines do nothing.
2142              
2143             =head1 ERRORS
2144              
2145             As with the rest of App::Fetchware, App::Fetchware::Util does not return any
2146             error codes; instead, all errors are die()'d if it's App::Fetchware::Util's
2147             error, or croak()'d if its the caller's fault. These exceptions are simple
2148             strings, and are listed in the L section below.
2149              
2150             =head1 BUGS
2151              
2152             App::Fetchware::Util's temporary directory creation utilities, create_tempdir(),
2153             original_cwd(), and cleanup_tempdir(), only keep track of one tempdir at a time. If
2154             you create another tempdir with create_tempdir() it will override the value of
2155             original_cwd(), which may mess up other functions that call create_tempdir(),
2156             original_cwd(), and cleanup_tempdir(). Therefore, be careful when you call these
2157             functions, and do B use them inside a fetchware extension if you reuse
2158             App::Fetchware's start() and end(), because App::Fetchware's start() and end()
2159             use these functions, so your use of them will conflict. If you still need to
2160             create a tempdir just call File::Temp's tempdir() directly.
2161              
2162             =head1 AUTHOR
2163              
2164             David Yingling
2165              
2166             =head1 COPYRIGHT AND LICENSE
2167              
2168             This software is copyright (c) 2013 by David Yingling.
2169              
2170             This is free software; you can redistribute it and/or modify it under
2171             the same terms as the Perl 5 programming language system itself.
2172              
2173             =cut
2174              
2175             __END__