1#
2# Authentic Theme (https://github.com/authentic-theme/authentic-theme)
3# Copyright Ilia Rostovtsev <programming@rostovtsev.io>
4# Licensed under MIT (https://github.com/authentic-theme/authentic-theme/blob/master/LICENSE)
5#
6use strict;
7
8use lib ("$ENV{'THEME_ROOT'}/lib");
9
10do("$ENV{'THEME_ROOT'}/authentic-funcs.pl");
11
12use Cwd 'abs_path';
13use Encode qw(decode encode);
14use File::Basename;
15use File::MimeInfo;
16use File::Find;
17use File::Copy;
18use File::Grep qw( fdo );
19use POSIX;
20
21our (%access,
22     %gconfig,
23     %in,
24     %text,
25     %theme_text,
26     @remote_user_info,
27     $base_remote_user,
28     $config_directory,
29     $current_theme,
30     %userconfig,
31     @allowed_paths,
32     $base,
33     $cwd,
34     $path,
35     $remote_user);
36our $checked_path;
37our $module_path;
38
39our %request_uri = get_request_uri();
40set_module($request_uri{'module'});
41get_libs($request_uri{'module'});
42
43sub set_module
44{
45    my ($module) = @_;
46
47    $module_path = get_env('document_root') . '/' . $module;
48
49    set_env('foreign_module_name',    $module);
50    set_env('foreign_root_directory', $module_path);
51}
52
53sub get_libs
54{
55    my ($module) = @_;
56
57    do($module_path . '/' . $module . '-lib.pl');
58
59    &ReadParse();
60
61    get_paths();
62
63    switch_to_user($in{'username'});
64
65    $checked_path = $path;
66    if (join(" , ", @allowed_paths) ne '/') {
67        $checked_path =~ s/$in{'cwd'}\//\//ig;
68    }
69
70    %text       = (load_language($current_theme), load_language($module), %text);
71    %theme_text = %text;
72}
73
74sub get_type
75{
76    my ($dir) = @_;
77    if (-d $dir) {
78        return 1;
79    } else {
80        return 0;
81    }
82}
83
84sub get_errors
85{
86    my %errors = %{ $_[0] };
87
88    if (scalar %errors) {
89        return convert_to_json(\%errors);
90    } else {
91        return undef;
92    }
93
94}
95
96sub get_request_uri
97{
98    (my $uri = get_env('request_uri')) =~ s/\?/&/;
99    my @r = split /&/, $uri;
100    my %c;
101
102    foreach (@r) {
103        my ($k, $v) = split /=/, $_;
104        $c{$k} = $v;
105    }
106
107    return %c;
108}
109
110sub get_user_config
111{
112    my ($k) = @_;
113
114    my %t;
115    read_file("$config_directory/$current_theme/settings-$remote_user", \%t);
116
117    if ($k) {
118        my $v = $t{$k};
119        $v =~ s/'|;//g;
120        return $v;
121    } else {
122        my %c = map {(my $v = $t{$_}) =~ s/'|;//g; $_ => $v} keys %t;
123        return %c;
124    }
125}
126
127sub kill_previous
128{
129    my $pid = tokenize($_[0]);
130    if ($pid) {
131        kill(9, $pid);
132    }
133    tokenize($_[0], $_[1]);
134}
135
136sub tokenize
137{
138    my ($key, $value) = @_;
139    my $salt = substr(encode_base64($main::session_id), 0, 16);
140    my %var;
141    my $user = $remote_user;
142    my $tmp_file;
143
144    $key =~ s/(?|([\w-]+$)|([\w-]+)\.)//;
145    $key = $1;
146    $key  =~ tr/A-Za-z0-9//cd;
147    $user =~ tr/A-Za-z0-9//cd;
148    $salt =~ tr/A-Za-z0-9//cd;
149
150    $tmp_file = tempname('.theme_' . $salt . '_' . get_product_name() . '_' . $key . '_' . $user);
151    $var{$key} = $value;
152
153    if ($value) {
154        write_file($tmp_file, \%var);
155    } else {
156        my %theme_temp_data;
157        read_file($tmp_file, \%theme_temp_data);
158        unlink_file($tmp_file);
159        return $theme_temp_data{$key};
160    }
161}
162
163sub get_pagination
164{
165
166    my ($page, $pages, $query) = @_;
167
168    our ($path);
169
170    my $search_follow_symlinks  = $in{'follow'};
171    my $search_case_insensitive = $in{'caseins'};
172    my $search_grep             = $in{'grepstring'};
173    my $fsid                    = $in{'fsid'};
174    my $exclude                 = $in{'exclude'};
175    my $regex                   = $in{'regex'};
176    my $all_items               = $in{'all_items'};
177
178    my $left  = $page - 2;
179    my $right = $page + 3;
180    my @range;
181    my $last;
182    my $pagination;
183    my $invisible = $pages == 1 ? ' invisible' : undef;
184
185    my $start = sub {
186        my $start;
187        my $disabled = ($page == 1 ? " disabled" : undef);
188
189        $start = "<div class=\"dataTables_paginate paging_simple_numbers spaginates$invisible\">";
190        $start .= '<ul class="pagination">';
191        $start .= "<li class='paginate_button previous$disabled'>";
192        $start .= '<a><i class="fa fa-fw fa-angle-left"></i></a>';
193        $start .= "</li>";
194        return $start;
195    };
196
197    my $current = sub {
198        my ($i) = @_;
199        my $end;
200        my $active = ($page eq $i ? " active" : undef);
201        $end = "<li class='paginate_button$active'>";
202        $end .=
203"<a class='spaginated' href='list.cgi?page=$i&path=@{[urlize($path)]}&query=@{[urlize($query)]}&follow=$search_follow_symlinks&caseins=$search_case_insensitive&grepstring=$search_grep&fsid=$fsid&exclude=$exclude&regex=$regex&all_items=$all_items'>@{[nice_number($i, ',')]}</a>";
204        $end .= "</li>";
205        return $end;
206    };
207
208    my $range = sub {
209        my $range;
210        $range = '<li class="paginate_button disabled">';
211        $range .= '<a>...</a>';
212        $range .= "</li>";
213
214    };
215
216    my $end = sub {
217        my $end;
218        my $disabled = ($page == $pages ? " disabled" : undef);
219        $end = "<li class='paginate_button next$disabled'>";
220        $end .= '<a><i class="fa fa-fw fa-angle-right"></i></a>';
221        $end .= "</li>";
222        $end .= '</ul>';
223        $end .= '</div>';
224        return $end;
225    };
226
227    for (my $i = 1; $i <= $pages; $i++) {
228        if ($i == 1 || $i == $pages || $i >= $left && $i < $right) {
229            push(@range, $i);
230        }
231    }
232
233    foreach my $i (@range) {
234        if ($last) {
235            if ($i - $last == 2) {
236                $pagination .= &$current($last + 1);
237            } elsif ($i - $last != 1) {
238                $pagination .= &$range();
239            }
240        }
241        $pagination .= &$current($i);
242        $last = $i;
243    }
244
245    $pagination = &$start() . $pagination . &$end();
246    return $pagination;
247}
248
249sub test_all_items_query
250{
251    return $in{'all_items'} eq '3' ? 3 : 0;
252}
253
254sub get_entries_list
255{
256    my @entries_list;
257    if (test_all_items_query()) {
258        if ($in{'query'}) {
259            @entries_list = exec_search('list');
260        } else {
261            find(
262                {
263                   wanted => sub {
264                       my $found = $File::Find::name;
265                       push(@entries_list, $_);
266                   },
267                },
268                $cwd);
269            @entries_list = grep {$_ ne '.' && $_ ne '..'} @entries_list;
270        }
271    } else {
272        @entries_list = split(/\0/, $in{'name'});
273    }
274    return @entries_list;
275}
276
277sub extra_query
278{
279    my $page       = $in{'page'};
280    my $query      = $in{'query'};
281    my $paginate   = $in{'paginate'};
282    my $follow     = $in{'follow'};
283    my $caseins    = $in{'caseins'};
284    my $grepstring = $in{'grepstring'};
285    my $fsid       = $in{'fsid'};
286    my $exclude    = $in{'exclude'};
287    my $regex      = $in{'regex'};
288    my $all_items  = $in{'all_items'};
289    return
290"&page=$page&query=$query&paginate=$paginate&follow=$follow&caseins=$caseins&grepstring=$grepstring&fsid=$fsid&exclude=$exclude&regex=$regex&all_items=$all_items";
291}
292
293sub set_response
294{
295    my ($c) = @_;
296    print "Set-Cookie: file-manager-response=" . $c . "; path=/\r\n";
297}
298
299sub set_response_count
300{
301    my ($c) = @_;
302    print "Set-Cookie: file-manager-response_count=" . $c . "; path=/\r\n";
303}
304
305sub fatal_errors
306{
307    my @errors = @_;
308
309    head();
310    print $text{'errors_occured'};
311    print "<ul>";
312    foreach my $error (@errors) {
313        print("<li>$error</li>");
314    }
315    print "</ul>";
316}
317
318sub redirect_local
319{
320    print "Location: $_[0]\n\n";
321}
322
323sub print_error
324{
325    my ($err_msg) = @_;
326    my %err;
327    $err{'error'} = $err_msg;
328    print_json([\%err]);
329    exit;
330}
331
332sub cache_search
333{
334    my ($id, $searched_data) = @_;
335    $id || return ();
336
337    my $tmp_dir       = tempname_dir();
338    my $fname         = ".$remote_user-file-manager-scache";
339    my $fcached       = $tmp_dir . "/$fname-$id";
340    my $dcached       = read_file_contents($fcached);
341    my $dcached_ready = $dcached ? unserialise_variable($dcached) : undef;
342    my @data;
343
344    # Clear previously cached data
345    opendir(my $dir, $tmp_dir);
346    my @tmps = grep {$_ =~ /$fname/} readdir($dir);
347    closedir $dir;
348    foreach (@tmps) {
349        my $file = "$tmp_dir/$_";
350        my @stat = stat($file);
351        if (@stat && $stat[9] < time() - (24 * 60 * 60)) {
352            unlink_file($file);
353        }
354    }
355
356    # Check if cache with requested id is available
357    if (!$searched_data && -r $fcached && @$dcached_ready) {
358
359        # Use cache for now
360        @data = @$dcached_ready;
361        if (@data) {
362            return @data;
363        } else {
364            return ();
365        }
366    } elsif ($searched_data) {
367
368        # Write cache
369        my $fh = "cache";
370        open_tempfile($fh, ">$fcached");
371        print_tempfile($fh, serialise_variable($searched_data));
372        close_tempfile($fh);
373    } else {
374        return ();
375    }
376}
377
378sub cache_search_delete
379{
380    my ($id, $deleted_data) = @_;
381
382    my @results_cached = cache_search($id);
383    if (@results_cached) {
384        @results_cached = grep {
385            my $f = $_;
386            !grep $f =~ /^\Q$_\E/, @$deleted_data
387        } @results_cached;
388        cache_search($id, \@results_cached);
389    }
390
391}
392
393sub cache_search_rename
394{
395    my ($id, $from, $to) = @_;
396
397    my @results_cached = cache_search($id);
398    if (@results_cached) {
399        my @updated_cache;
400        foreach my $file (@results_cached) {
401            if ($file eq "/$from") {
402                $file = "/$to";
403            }
404            push(@updated_cache, $file);
405        }
406        cache_search($id, \@updated_cache);
407    }
408}
409
410sub exec_search
411{
412    my ($list)  = @_;
413    my $mask    = $in{'query'};
414    my $grep    = $in{'grepstring'};
415    my $fsid    = $in{'fsid'};
416    my $exclude = $in{'exclude'};
417    my $replace = $in{'grepreplace'};
418    my $caseins = $in{'caseins'};
419    my $follow  = ($in{'follow'} || $in{'limit_type'} == 3 ? 1 : 0);
420    my $regex   = ($in{'regex'}                            ? 1 : 0);
421    my $ouser   = $in{'limit_user'};
422    my $ogroup  = $in{'limit_group'};
423    my $otype   = $in{'limit_type'};
424    my $osize   = $in{'limit_size'};
425    my @results;
426    my @excludes;
427
428    my @results_cached = cache_search($fsid);
429    if (@results_cached) {
430        return @results_cached;
431    }
432
433    find(
434        {
435           wanted => sub {
436               my $found = $File::Find::name;
437               if ($found ne $path) {
438                   my $found_text = $_;
439                   my $mask_text  = $mask;
440                   if ($caseins) {
441                       $found_text = lc($found_text);
442                       $mask_text  = lc($mask_text);
443                       $exclude    = lc($exclude);
444                   }
445                   if ($exclude) {
446                       @excludes = split(';', $exclude);
447                   }
448                   if (($mask_text eq "*" || !$regex && (index($found_text, $mask_text) != -1)) ||
449                       ($regex && $found_text =~ /$mask_text/))
450                   {
451                       if (!$list) {
452                           $found =~ s/^\Q$cwd\E//g;
453                       }
454                       if ($follow || (!$follow && !-l $_)) {
455                           my $excluded;
456                           my $found_ = $found;
457                           $found_ = lc($found_) if ($caseins);
458                           if (@excludes) {
459                               foreach my $e (@excludes) {
460                                   if ((!$regex && index($found_, $e) != -1) || ($regex && $found_ =~ /$e/)) {
461                                       $excluded = 1;
462                                   }
463                               }
464                           }
465                           my $extra_exclude;
466                           if ($ouser || $ogroup || $osize) {
467                               my $found_cwd  = &simplify_path("$cwd/$found");
468                               my @found_stat = stat($found_cwd);
469                               if ($ouser && $ouser ne getpwuid($found_stat[4])) {
470                                   $extra_exclude = 1;
471                               }
472                               if ($ogroup && $ogroup ne getgrgid($found_stat[5])) {
473                                   $extra_exclude = 1;
474                               }
475                               if ($osize) {
476                                   my ($osize_operator)    = $osize =~ /\s*([\D]+)\d/;
477                                   my ($osize_size)        = $osize =~ /\s*([\d]+)\s*/;
478                                   my ($osize_unit)        = $osize =~ /\s*\d+\s*(\p{L}+)/;
479                                   my ($osize_operator_bi) = $osize =~ /\s*[\D]+\d.*?-\s*([\D]+)\d/;
480                                   my ($osize_size_bi)     = $osize =~ /\s*[\d]+\s*.*?-.*?([\d]+)\s*/;
481                                   my ($osize_unit_bi)     = $osize =~ /\s*\d+\s*\p{L}+\s*-.*?[\d]+\s*(\p{L}+)/;
482                                   my $file_size           = $found_stat[7];
483                                   my $osize_size_format   = sub {
484                                       my ($unit, $size) = @_;
485                                       if ($unit eq lc($theme_text{'theme_xhred_nice_size_kB'}) ||
486                                           $unit eq lc($theme_text{'theme_xhred_nice_size_kIB'}) ||
487                                           string_starts_with($unit, "k") ||
488                                           string_starts_with($unit, lc($theme_text{'theme_xhred_nice_size_kB'})))
489                                       {
490                                           $size *= 1024;
491                                       } elsif ($unit eq lc($theme_text{'theme_xhred_nice_size_MB'}) ||
492                                                $unit eq lc($theme_text{'theme_xhred_nice_size_MIB'}) ||
493                                                string_starts_with($unit, "m") ||
494                                                string_starts_with($unit, lc($theme_text{'theme_xhred_nice_size_MB'})))
495                                       {
496                                           $size *= 1024 * 1024;
497                                       } elsif ($unit eq lc($theme_text{'theme_xhred_nice_size_GB'}) ||
498                                                $unit eq lc($theme_text{'theme_xhred_nice_size_GIB'}) ||
499                                                string_starts_with($unit, "g") ||
500                                                string_starts_with($unit, lc($theme_text{'theme_xhred_nice_size_GB'})))
501                                       {
502                                           $size *= 1024 * 1024 * 1024;
503                                       } elsif ($unit eq lc($theme_text{'theme_xhred_nice_size_TB'}) ||
504                                                $unit eq lc($theme_text{'theme_xhred_nice_size_TIB'}) ||
505                                                string_starts_with($unit, "t") ||
506                                                string_starts_with($unit, lc($theme_text{'theme_xhred_nice_size_TB'})))
507                                       {
508                                           $size *= 1024 * 1024 * 1024 * 1024;
509                                       } elsif ($unit eq lc($theme_text{'theme_xhred_nice_size_PB'}) ||
510                                                $unit eq lc($theme_text{'theme_xhred_nice_size_PIB'}) ||
511                                                string_starts_with($unit, "p") ||
512                                                string_starts_with($unit, lc($theme_text{'theme_xhred_nice_size_PB'})))
513                                       {
514                                           $size *= 1024 * 1024 * 1024 * 1024 * 1024;
515                                       }
516                                       return $size;
517
518                                   };
519                                   my $extra_exclude_test = sub {
520                                       my ($operator, $osize, $fsize) = @_;
521                                       my $exclude;
522                                       if ($operator eq '<') {
523                                           if ($osize <= $fsize) {
524                                               $exclude = 1;
525                                           }
526                                       } elsif ($operator eq '<=') {
527                                           if ($osize < $fsize) {
528                                               $exclude = 1;
529                                           }
530                                       } elsif ($operator eq '>') {
531                                           if ($osize >= $fsize) {
532                                               $exclude = 1;
533                                           }
534                                       } elsif ($operator eq '>=') {
535                                           if ($osize > $fsize) {
536                                               $exclude = 1;
537                                           }
538                                       } elsif ($operator eq '!=') {
539                                           if ($osize == $fsize) {
540                                               $exclude = 1;
541                                           }
542
543                                       } elsif (!$operator || $operator eq '=' || $operator eq '==') {
544                                           if ($osize != $fsize) {
545                                               $exclude = 1;
546                                           }
547                                       }
548                                       return $exclude;
549                                   };
550                                   if ($otype == 2 && -d $found_cwd) {
551                                       $file_size = recursive_disk_usage($found_cwd);
552                                   }
553                                   $osize_operator    = trim($osize_operator);
554                                   $osize_size        = int(trim($osize_size));
555                                   $osize_unit        = lc(trim($osize_unit));
556                                   $osize_operator_bi = trim($osize_operator_bi);
557                                   $osize_size_bi     = int(trim($osize_size_bi));
558                                   $osize_unit_bi     = lc(trim($osize_unit_bi));
559                                   if ($osize_size) {
560                                       my $osize_size_ = $osize_size;
561                                       if ($osize_unit) {
562                                           $osize_size_ = &$osize_size_format($osize_unit, $osize_size_);
563                                       }
564                                       $extra_exclude = &$extra_exclude_test($osize_operator, $osize_size_, $file_size);
565                                       if (!$extra_exclude) {
566                                           if ($osize_size && $osize_size_bi) {
567                                               my $osize_size_bi_ = $osize_size_bi;
568                                               if ($osize_unit_bi) {
569                                                   $osize_size_bi_ = &$osize_size_format($osize_unit, $osize_size_bi_);
570                                               }
571                                               $extra_exclude =
572                                                 &$extra_exclude_test($osize_operator_bi, $osize_size_bi_, $file_size);
573                                           }
574                                       }
575                                   }
576                               }
577                           }
578                           if ($otype) {
579                               my $found_cwd = &simplify_path("$cwd/$found");
580                               if ($otype == 1 && !-f $found_cwd) {
581                                   $extra_exclude = 1;
582                               }
583                               if ($otype == 2 && !-d $found_cwd) {
584                                   $extra_exclude = 1;
585                               }
586                               if ($otype == 3 && !-l $found_cwd) {
587                                   $extra_exclude = 1;
588                               }
589                           }
590                           if (!$extra_exclude && (!$exclude || (@excludes && !$excluded))) {
591                               push(@results, $found);
592                           }
593                       }
594                   }
595               }
596           },
597           follow      => $follow,
598           follow_skip => 2,
599        },
600        $cwd);
601
602    my @replaces;
603    if (length($grep) || length($replace)) {
604        if (length($grep)) {
605            @results = map {&simplify_path("$cwd/$_")} @results;
606            my @matched;
607            fdo {
608                my ($file, $line, $text) = @_;
609                if ($caseins) {
610                    $text = lc($text);
611                    $grep = lc($grep);
612                }
613                if ((!$regex && index($text, $grep) != -1) || ($regex && $text =~ /$grep/)) {
614                    if (!grep(/^\Q$results[$file]\E$/, @replaces)) {
615                        push(@replaces, $results[$file]);
616                    }
617                    (my $sfile = $results[$file]) =~ s/^\Q$cwd\E//g;
618                    if (!grep(/^\Q$sfile\E$/, @matched)) {
619                        push(@matched, $sfile);
620                    }
621                }
622            }
623            @results;
624            undef(@results);
625            @results = @matched;
626        }
627        if (length($replace)) {
628            foreach my $file (@replaces) {
629                if (-r $file) {
630                    if ($caseins) {
631                        (my $fc = read_file_contents($file)) =~ s/$grep/$replace/gi;
632                        write_file_contents($file, $fc);
633                    } else {
634                        (my $fc = read_file_contents($file)) =~ s/$grep/$replace/g;
635                        write_file_contents($file, $fc);
636                    }
637                }
638            }
639        }
640    }
641    cache_search($fsid, \@results) if ($fsid && !length($replace));
642    return @results;
643}
644
645sub server_pagination_enabled
646{
647    my ($totals, $max_allowed, $query) = @_;
648    return ($totals > $max_allowed || ($query && $totals));
649}
650
651sub print_content
652{
653    my %list_data;
654    my $query = $in{'query'};
655    my @list;
656    my $clear_path = sub {
657        my ($path) = @_;
658        $path =~ s/[\/]+/\//g;
659        return $path;
660    };
661
662    # In case of search trim the list accordingly
663    if ($query) {
664        @list = exec_search();
665    } else {
666        unless (opendir(DIR, $cwd)) {
667            print_error("$text{'theme_xhred_global_error'}: [tt]`$cwd`[/tt]- $!.");
668            exit;
669        }
670        @list = grep {$_ ne '.' && $_ ne '..'} readdir(DIR);
671        closedir(DIR);
672    }
673
674    # Filter out not allowed entries
675    if (test_allowed_paths()) {
676
677        # Leave only allowed
678        my @allowed_list;
679        for my $allowed_path (@allowed_paths) {
680            push(
681                @allowed_list,
682                grep {
683                    my $list_path = &$clear_path("$cwd/$_");
684                    $list_path      =~ /^\Q$allowed_path\E\// ||
685                      $allowed_path =~ /^\Q$list_path\E/
686                } @list);
687        }
688
689        # Remove duplicates
690        my %hash = map {$_, 1} @allowed_list;
691        @list = keys %hash;
692    }
693
694    my $page      = 1;
695    my $pagelimit = 4294967295;
696    my $pages     = 0;
697
698    my $max_allowed = int($userconfig{'max_allowed'});
699    if ($max_allowed !~ /^[0-9,.E]+$/ || $max_allowed < 100 || $max_allowed > 10000) {
700        $max_allowed = 1000;
701    }
702
703    my $totals         = scalar(@list);
704    my $totals_spliced = $totals;
705
706    my $tuconfig_per_page = get_user_config('config_portable_module_filemanager_records_per_page');
707
708    if (server_pagination_enabled($totals, $max_allowed, $query)) {
709        $page      = int($in{'page'})     || 1;
710        $pagelimit = int($in{'paginate'}) || int($tuconfig_per_page) || 30;
711        $pages     = ceil(($totals) / $pagelimit);
712        if ($page > $pages) {
713            $page = $pages;
714            $in{'page'} = $page;
715        }
716        my $splice_start = $pagelimit * ($page - 1);
717        my $splice_end   = $pagelimit;
718        if ($totals > 100000) {
719            @list = sort {$a cmp $b} @list;
720        } else {
721            @list =
722              map $_->[0], sort {$a->[1] <=> $b->[1] || $a->[0] cmp $b->[0]}
723              map [$_, -f "$cwd/$_"],
724              @list;
725        }
726        @list           = splice(@list, $splice_start, $splice_end);
727        $totals_spliced = scalar(@list);
728    }
729
730    @list = map {&simplify_path("$cwd/$_")} @list;
731
732    my %acls;
733    my %attributes;
734    my $setype = get_selinux_command_type();
735    my %secontext;
736
737    # List ACLs
738    if ($userconfig{'columns'} =~ /acls/ && get_acls_status()) {
739        my $command = get_list_acls_command() . " " . join(' ', map {quotemeta("$_")} @list);
740        my $output  = `$command`;
741        my @aclsArr;
742        foreach my $aclsStr (split(/\n\n/, $output)) {
743            $aclsStr =~ /#\s+file:\s*(.*)/;
744            my ($file) = ($aclsStr =~ /#\s+file:\s*(.*)/);
745            my @aclsA = ($aclsStr =~ /^(?!(#|user::|group::|other::))([\w\:\-\_]+)/gm);
746            push(@aclsArr, [$file, \@aclsA]);
747        }
748        %acls = map {$_->[0] => ('<span data-acls>' . join("<br>", (grep /\S/, @{ $_->[1] })) . '</span>')} @aclsArr;
749    }
750
751    # List attributes
752    if ($userconfig{'columns'} =~ /attributes/ && get_attr_status()) {
753        my $command =
754          get_attr_command() . join(' ', map {quotemeta("$_")} @list);
755        my $output = `$command`;
756        my @attributesArr =
757          map {[split(/\s+/, $_, 2)]} split(/\n/, $output);
758        %attributes = map {$_->[1] => ('<span data-attributes>' . $_->[0] . '</span>')} @attributesArr;
759    }
760
761    # List security context
762    if ($userconfig{'columns'} =~ /selinux/ && get_selinux_status()) {
763        my $command =
764          get_selinux_command() . join(' ', map {quotemeta("$_")} @list);
765        my $output = `$command`;
766        (!$setype && ($output =~ s/\n//g, $output =~ s/,\s/,/g));
767        my $delimiter = ($setype ? '\n' : ',');
768        my @searray =
769          map {[split(/\s+/, $_, 2)]} split(/$delimiter/, $output);
770        %secontext =
771          map {$_->[1] => ($_->[0] eq "?" ? undef : ('<span data-secontext>' . $_->[0] . '</span>'))} @searray;
772    }
773
774    # Get info about directory entries
775    my @info    = map {[$_, lstat($_), &mimetype($_), -d, -l $_, $secontext{$_}, $attributes{$_}, $acls{$_}]} @list;
776    my @folders = map {$_} grep {$_->[15] == 1} @info;
777    my @files   = map {$_} grep {$_->[15] != 1} @info;
778
779    if (server_pagination_enabled($totals, $max_allowed, $query)) {
780        undef(@list);
781        push(@list, @info);
782    } else {
783        @folders = sort {"\L$a->[0]" cmp "\L$b->[0]"} @folders;
784        @files   = sort {"\L$a->[0]" cmp "\L$b->[0]"} @files;
785        undef(@list);
786        push(@list, @folders, @files);
787    }
788
789    my $info_total;
790    my $info_files   = scalar(@files);
791    my $info_folders = scalar(@folders);
792
793    my @allowed_for_edit = split(/\s+/, $access{'allowed_for_edit'});
794    my %allowed_for_edit = map {$_ => 1} @allowed_for_edit;
795
796    # Set icons variables
797    my $edit_icon    = "<i class='fa fa-edit' alt='$text{'edit'}'></i>";
798    my $rename_icon  = "<i class='fa fa-font' title='$text{'rename'}'></i>";
799    my $extract_icon = "<i class='fa fa-external-link' alt='$text{'extract_archive'}'></i>";
800    my $goto_icon    = "<i class='fa fa-arrow-right' alt='$text{'goto_folder'}'></i>";
801
802    my $server_pagination = undef;
803    $list_data{'pagination_limit'} = undef;
804
805    if (server_pagination_enabled($totals, $max_allowed, $query)) {
806        $page      = int($in{'page'})     || 1;
807        $pagelimit = int($in{'paginate'}) || int($tuconfig_per_page) || 30;
808        $pages     = ceil(($totals) / $pagelimit);
809        if ($page > $pages) {
810            $page = $pages;
811            $in{'page'} = $page;
812        }
813        $server_pagination = get_pagination($page, $pages, $query);
814        $list_data{'pagination_limit'} = $in{'paginate'} || undef;
815
816        my $pagination_text = $text{'theme_xhred_datatable_sinfo'};
817        my $start           = $page * $pagelimit - $pagelimit + 1;
818        my $end             = $page * $pagelimit;
819        if ($end > $totals) {
820            $end = $totals;
821        }
822        $pagination_text =~ s/_START_/@{[nice_number($start, ",")]}/ig;
823        $pagination_text =~ s/_END_/@{[nice_number($end, ",")]}/ig;
824        $pagination_text =~ s/_TOTAL_/@{[nice_number($totals, ",")]}/ig;
825        $list_data{'pagination_text'} = $pagination_text;
826    }
827
828    $list_data{'pagination'} = $server_pagination;
829    my $total_with_pagination;
830    if ($server_pagination) {
831        $total_with_pagination = "_paginated";
832    }
833
834    if ($info_files eq 1 && $info_folders eq 1) {
835        $info_total = ('filemanager_global_info' . $total_with_pagination . '_total1');
836    } elsif ($info_files ne 1 && $info_folders eq 1) {
837        $info_total = ('filemanager_global_info' . $total_with_pagination . '_total2');
838    } elsif ($info_files eq 1 && $info_folders ne 1) {
839        $info_total = ('filemanager_global_info' . $total_with_pagination . '_total3');
840    } else {
841        $info_total = ('filemanager_global_info' . $total_with_pagination . '_total4');
842    }
843    $list_data{'total'} = "<div class='total'>"
844      .
845      ( $query ? (trim($text{'filemanager_global_search_results'}) . ": ") :
846          ($server_pagination ? (trim($text{'filemanager_global_paginated_results'}) . ": ") : undef)
847      ) .
848      ""
849      .
850      (
851        text(nice_number($info_total,   ","),
852             nice_number($info_files,   ","),
853             nice_number($info_folders, ","),
854             nice_number($totals,       ","),
855             nice_number($pages,        ",")
856        )
857      ) .
858      "</div>";
859
860    # Render current directory entries
861    $list_data{'form'} = &ui_form_start("", "post", undef, "id='list_form'");
862
863    my @ui_columns = ('<input class="_select-unselect_" type="checkbox" onclick="selectUnselect(this)" />', '');
864    push @ui_columns, ('<span data-head-name>' . $text{'name'} . '</span>');
865    push @ui_columns, ('<span data-head-type>' . $text{'type'} . '</span>')
866      if ($userconfig{'columns'} =~ /type/);
867    push @ui_columns, ('<span data-head-actions>' . $text{'actions'} . '</span>');
868    push @ui_columns, ('<span data-head-size>' . $text{'size'} . '</span>')
869      if ($userconfig{'columns'} =~ /size/);
870    push @ui_columns, ('<span data-head-owner_user>' . $text{'ownership'} . '</span>')
871      if ($userconfig{'columns'} =~ /owner_user/);
872    push @ui_columns, ('<span data-head-permissions>' . $text{'permissions'} . '</span>')
873      if ($userconfig{'columns'} =~ /permissions/);
874    push @ui_columns, ('<span data-head-acls>' . $text{'acls'} . '</span>')
875      if (get_acls_status() && $userconfig{'columns'} =~ /acls/);
876    push @ui_columns, ('<span data-head-attributes>' . $text{'attributes'} . '</span>')
877      if (get_attr_status() && $userconfig{'columns'} =~ /attributes/);
878    push @ui_columns, ('<span data-head-selinux>' . $text{'selinux'} . '</span>')
879      if (get_selinux_status() && $userconfig{'columns'} =~ /selinux/);
880    push @ui_columns, ('<span data-head-last_mod_time>' . $text{'last_mod_time'} . '</span>')
881      if ($userconfig{'columns'} =~ /last_mod_time/);
882
883    $list_data{'rows'} = '';
884    for (my $count = 1; $count <= $totals_spliced; $count++) {
885        if ($count > $totals) {last;}
886        my $class = $count & 1 ? "odd" : "even";
887        my $link  = $list[$count - 1][0];
888        $link =~ s/\Q$cwd\E\///;
889        $link =~ s/^\///g;
890        my $vlink = html_escape($link);
891        my $hlink = html_escape($vlink);
892
893        my $filename = $link;
894        $filename =~ /\/([^\/]+)$/;
895        if ($1 && $list[$count - 1][15] == 0) {
896            $filename = $1;
897        }
898        my $hlink_path = $hlink;
899        if ($query) {
900            if (!string_contains($hlink_path, '/') && $list[$count - 1][15] == 0) {
901                $hlink_path = undef;
902            }
903            $hlink_path =~ s/\/\Q$filename\E$//;
904        }
905
906        my $type = $list[$count - 1][14];
907        $type =~ s/\//\-/g;
908        my $img = "images/icons/mime/$type.png";
909        unless (-e $request_uri{'module'} . '/' . $img) {
910            $img = "images/icons/mime/unknown.png";
911        }
912
913        my $actions =
914"<a class='action-link' href='javascript:void(0)' onclick='renameDialog(\"$hlink\")' title='$text{'rename'}' data-container='body'>$rename_icon</a>";
915        my $href;
916        my $is_archive = 0;
917        my $is_file    = 1;
918        my $is_gpg     = 0;
919        my $is_img     = 0;
920        if ($list[$count - 1][15] == 1) {
921            $is_file = 0;
922            $href    = "index.cgi?path=" . &urlize("$path/$link");
923        } else {
924            my ($fname, $fpath, $fsuffix) =
925              fileparse($list[$count - 1][0]);
926            if ($base ne '/') {
927                $fpath =~ s/^\Q$base\E//g;
928            }
929            $href = "download.cgi?file=" . &urlize($link) . "&path=" . &urlize($fpath);
930            if ($0 =~ /search.cgi/) {
931                $actions =
932                  "$actions<a class='action-link' " .
933                  "href='index.cgi?path=" . &urlize($fpath) . "' " . "title='$text{'goto_folder'}'>$goto_icon</a>";
934            }
935            if ($type =~ /text-/ ||
936                $type =~ /svg\+xml/ ||
937                exists($allowed_for_edit{$type}))
938            {
939                $actions =
940                  "$actions<a class='action-link' href='edit_file.cgi?file=" . &urlize($link) .
941                  "&path=" . &urlize($path) . "' title='$text{'edit'}' data-container='body'>$edit_icon</a>";
942            }
943            my $type_archive = $type;
944            if ($type =~ /^image/) {
945                $is_img = 1;
946            }
947            if ($type =~ /application-pgp-encrypted/) {
948                my $link_gpg = $link;
949                $link_gpg =~ s/\.(gpg|pgp)$//;
950                $type_archive = mimetype($link_gpg);
951                $is_gpg       = 1;
952            }
953            if ($type_archive =~ /application-zip/ ||
954                $type_archive =~ /application-x-7z-compressed/              ||
955                $type_archive =~ /application-x-rar|application-vnd\.rar/   ||
956                $type_archive =~ /application-x-rpm/                        ||
957                $type_archive =~ /application-x-deb|debian\.binary-package/ ||
958                $type_archive =~ /x-compressed-tar/                         ||
959                $type_archive =~ /-x-tar/                                   ||
960                $type_archive =~ /-x-bzip/                                  ||
961                $type_archive =~ /-gzip/                                    ||
962                $type_archive =~ /-x-xz/)
963            {
964                $is_archive = 1;
965                $actions =
966                  "$actions <a class='action-link' href='extract.cgi?path=" . &urlize($path) .
967                  "&file=" . &urlize($link) . "' title='$text{'extract_archive'}' data-container='body'>$extract_icon</a> ";
968            }
969        }
970        my @row_data = ("<a href='$href' data-filemin-link=\"$hlink\"" .
971                          ($query ? " data-filemin-flink=\"$hlink_path\"" : undef) . "><img src=\"$img\"></a>",
972                        "<a href=\"$href\" data-filemin-link=\"$hlink\"" .
973                          ($query ? " data-filemin-flink=\"$hlink_path\"" : undef) . ">$vlink</a>");
974        my @td_tags = (undef,
975                       'class="col-icon"',
976                       'class="col-name" data-xarchive="' .
977                         $is_archive . '" data-xfile="' . $is_file . '" data-gpg="' . $is_gpg .
978                         '" data-img="' . $is_img . '" data-order="' . ($is_file ? 1 : 0) . html_escape($filename) . '"');
979        if ($userconfig{'columns'} =~ /type/) {
980            push(@row_data, $type);
981            push(@td_tags,  'class="col-type"');
982        }
983        push @row_data, $actions;
984        push(@td_tags, 'class="col-actions"');
985
986        if ($userconfig{'columns'} =~ /size/) {
987            my $size = &theme_nice_size_local($list[$count - 1][8]);
988            push @row_data,
989              (
990"<span data-toggle=\"tooltip\" data-html=\"true\" data-title=\"$text{'theme_xhred_filemanager_global_size_in_bytes'}<br>@{[nice_number($list[$count - 1][8])]}\">"
991                  . $size . "</span>");
992            push(@td_tags, 'class="col-size"');
993        }
994        if ($userconfig{'columns'} =~ /owner_user/) {
995            my $user;
996            my $group;
997            if (supports_users()) {
998                my $uid = getpwuid($list[$count - 1][5]);
999                my $gid = getgrgid($list[$count - 1][6]);
1000                $user  = $uid ? $uid : $list[$count - 1][5];
1001                $group = $gid ? $gid : $list[$count - 1][6];
1002            } else {
1003                $user  = $list[$count - 1][5];
1004                $group = $list[$count - 1][6];
1005            }
1006            push @row_data,
1007              (
1008"<span data-toggle=\"tooltip\" data-html=\"true\" data-title=\"$text{'filemanager_global_user_group_id'}<br>$list[$count - 1][5]:$list[$count - 1][6]\">"
1009                  . $user . ':' . $group . "</span>");
1010            push(@td_tags, 'class="col-ownership"');
1011        }
1012
1013        if ($userconfig{'columns'} =~ /permissions/) {
1014            my $permissions = sprintf("%04o", $list[$count - 1][3] & 07777);
1015            push @row_data, $permissions;
1016            push(@td_tags, 'class=col-permissions');
1017        }
1018
1019        if (get_acls_status() && $userconfig{'columns'} =~ /acls/) {
1020            push @row_data, $list[$count - 1][19];
1021            push(@td_tags, 'class="col-acls"');
1022        }
1023
1024        if (get_attr_status() && $userconfig{'columns'} =~ /attributes/) {
1025            push @row_data, $list[$count - 1][18];
1026            push(@td_tags, 'class="col-attrs"');
1027        }
1028        if (get_selinux_status() && $userconfig{'columns'} =~ /selinux/) {
1029            push @row_data, $list[$count - 1][17];
1030            push(@td_tags, 'class="col-selinux"');
1031        }
1032
1033        if ($userconfig{'columns'} =~ /last_mod_time/) {
1034            my $access_time = POSIX::strftime('%Y/%m/%d - %T', localtime($list[$count - 1][9]));
1035            my $mod_time    = POSIX::strftime('%Y/%m/%d - %T', localtime($list[$count - 1][10]));
1036            my $change_time = POSIX::strftime('%Y/%m/%d - %T', localtime($list[$count - 1][11]));
1037            push @row_data,
1038              (
1039"<span data-toggle=\"tooltip\" data-html=\"true\" data-title=\"$text{'filemanager_global_access_change_time'}<br>$access_time<br>$change_time\">"
1040                  . $mod_time . "</span>");
1041            push(@td_tags, 'data-order="' . ($list[$count - 1][10]) . '" class="col-time"');
1042        }
1043
1044        $list_data{'rows'} .= &ui_checked_columns_row(\@row_data, \@td_tags, "name", $vlink);
1045    }
1046
1047    $list_data{'form'} .= &ui_hidden("path", $path), "\n";
1048    $list_data{'form'} .= '</form>';
1049    $list_data{'success'}              = (length $in{'success'}     ? $in{'success'}     : undef);
1050    $list_data{'error'}                = (length $in{'error'}       ? $in{'error'}       : undef);
1051    $list_data{'error_fatal'}          = (length $in{'error_fatal'} ? $in{'error_fatal'} : undef);
1052    $list_data{'output'}               = (length $in{'output'}      ? $in{'output'}      : undef);
1053    $list_data{'page_requested'}       = $page;
1054    $list_data{'pagination_requested'} = $in{'paginate'};
1055    $list_data{'totals'}               = $totals;
1056    $list_data{'searched'}             = $query                 ? 1 : 0;
1057    $list_data{'flush'}                = test_all_items_query() ? 1 : 0;
1058    $list_data{'flush_reset'}          = $in{'flush_reset'}     ? 1 : 0;
1059    $list_data{'udata'} = { user          => $remote_user_info[0],
1060                            home          => $remote_user_info[7],
1061                            uid           => $remote_user_info[2],
1062                            guid          => $remote_user_info[3],
1063                            allowed_paths => \@allowed_paths,
1064                            base          => $base,
1065                            access        => $access{'work_as_user'} };
1066
1067    print_json([\%list_data]);
1068}
1069
1070sub get_tree
1071{
1072    my ($p, $d, $e, $y) = @_;
1073    my %r;
1074    my @r;
1075    my $ic;
1076    my $rp;
1077    my $df = int($d);
1078    my @ap = @allowed_paths;
1079    my $fr = scalar(@ap) > 1;
1080    my @af = length($p) ? ($p) : @ap;
1081    my $fu = scalar(@af) == 1;
1082
1083    # Check the queried path is allowed in the first place
1084    if (length($p)) {
1085        return \@r if (grep {$_ =~ /^\Q$p\E/} @ap);
1086    }
1087
1088    my $wanted = sub {
1089        my $td = $File::Find::name;
1090        if (-d $td && !-l $td) {
1091            my $push_label = sub {
1092                my ($td, $afic) = @_;
1093                my ($pd, $cd)   = $td =~ m|^ (.+) / ([^/]+) \z|x;
1094                my $pp = ($fu && $afic ne '/') ? $afic : undef;
1095                my $c  = $r{$td} =
1096                  { key => html_escape("$pp/$td"), title => (defined($cd) ? html_escape($cd) : html_escape($td)) };
1097                defined $pd ? (push @{ $r{$pd}{'children'} }, $c) : (push @r, $c);
1098            };
1099
1100            my $dc = $td =~ tr[/][];
1101            my ($ix) = grep {$af[$_] eq $td} (0 .. @af - 1);
1102            $ic = $ix if (defined($ix));
1103            my $afic = $af[$ic];
1104            if (!grep {$afic =~ /^\Q$_\E/} @ap &&
1105                !grep {$td =~ /^\Q$_\E/} @ap)
1106            {
1107                return;
1108            }
1109
1110            # Exclude non essentials on start
1111            if ($e && $afic eq '/' && $dc == 1) {
1112                if ($td =~ /^\/(cdrom|dev|lib|lost\+found|proc|run|snaps|sys|tmp|.trash)/i) {
1113                    return;
1114                }
1115            }
1116
1117            # Home directory only
1118            if ($fu) {
1119                $td =~ s/^\Q$afic\E//;
1120            }
1121
1122            # Starting with sub-directory in multiple allowed paths
1123            elsif ($y && $fr && defined($ix) && $dc > 1) {
1124                my $tdx  = $td;
1125                my @tdxs = split('/', $tdx);
1126                my @tdxss;
1127                for (my $i = 1; $i <= ($dc - 1); $i++) {
1128                    push(@tdxss, $tdxs[$i]);
1129                    my $tdxx = join("/", @tdxss);
1130                    $tdxx =~ s|^\Q/\E/?||;
1131                    &$push_label($tdxx, $afic);
1132                }
1133            }
1134            $td =~ s|^\Q/\E/?||;
1135            if ($r{$td} || !$td) {
1136                return;
1137            }
1138            &$push_label($td, $afic);
1139        }
1140    };
1141    my $preprocess = sub {
1142        my $td = $File::Find::name;
1143        my $dc = $td      =~ tr[/][];
1144        my $xc = $af[$ic] =~ tr[/][];
1145        my $dd = ($p || ($y && $af[$ic] ne '/')) ? $df + $xc : $df + 1;
1146        if ($dd) {
1147            if ($dc < $dd) {
1148                return sort {"\L$a" cmp "\L$b"} @_;
1149            }
1150            return;
1151        }
1152        return sort {"\L$a" cmp "\L$b"} @_;
1153    };
1154    find(
1155         {  wanted     => $wanted,
1156            preprocess => $preprocess
1157         },
1158         @af);
1159    return \@r;
1160}
1161
1162sub paster
1163{
1164    my ($c, $f, $s, $d, $r, $m, $z) = @_;
1165    my $x;
1166    my $j  = $c . ($f =~ m/^\// ? undef : '/') . $f;
1167    my $zz = sub {
1168        my ($q, $h) = @_;
1169        my ($u, $g);
1170        (undef, undef, $u) = getpwnam($h);
1171        $g = getgrnam($h);
1172        if (defined($u) && defined($g)) {
1173            system("chown -R $u:$g " . quotemeta("$q"));
1174        }
1175    };
1176
1177    if (!$r && -f $j ne -d $j) {
1178        for (my $t = 1;; $t += 1) {
1179            my ($jn, $je) = $j =~ /(.*)\.(.*)/;
1180            if (!-e ($jn . '(' . $t . ')' . ".$je") && (!-e ($j . '(' . $t . ')'))) {
1181                $x = $t;
1182                last;
1183            }
1184        }
1185    }
1186    $s =~ s/\/\//\//g;
1187    if ($m && -d $j && $j =~ /^\Q$s\E/) {
1188        set_response('merr');
1189        return;
1190    }
1191    if (-d $j) {
1192        $j = $j . (!$x ? '' : '(' . $x . ')');
1193    } else {
1194        my ($jn, $je) = $j =~ /(.*)\.(.*)/;
1195        if ($je) {
1196            $j = $jn . (!$x ? '' : '(' . $x . ')') . ".$je";
1197        } else {
1198            $j = $j . (!$x ? '' : '(' . $x . ')');
1199        }
1200    }
1201    my ($o, $e);
1202    if ($m) {
1203        $o = move($s, $j);
1204        &$zz($j, $z) if ($o);
1205        if (!$o && $!) {
1206            $e = $!;
1207        }
1208    } else {
1209        ($o, $e) = copy_source_dest($s, $j);
1210        &$zz($j, $z) if ($o);
1211    }
1212    if ($x) {
1213        set_response('cc');
1214    }
1215
1216    return $e;
1217
1218}
1219
1220sub get_element_index
1221{
1222    my ($arr, $elem) = @_;
1223    my $idx;
1224    for my $i (0 .. $#$arr) {
1225        if ($arr->[$i] eq $elem) {
1226            $idx = $i;
1227            last;
1228        }
1229    }
1230    return $idx;
1231}
1232
1233sub get_tar_verbatim
1234{
1235    my $test_param = 'verbatim-files-from';
1236    my $out        = &backquote_command("tar --help |grep $test_param");
1237    if ($out && $out =~ /$test_param/m) {
1238        return " --$test_param";
1239    }
1240    return "";
1241}
1242
1243sub get_gpg_version
1244{
1245    my ($gpg) = @_;
1246    $gpg = "gpg" if (!$gpg);
1247    $gpg = quotemeta($gpg);
1248    $gpg = `$gpg --version`;
1249    $gpg =~ /(\*|\d+(\.\d+){0,2})/;
1250    return $1;
1251}
1252
1253sub get_gpg_path
1254{
1255    my $gnupg        = 'gnupg';
1256    my $gnupg_target = foreign_available($gnupg) ? $gnupg : get_product_name();
1257    my %gpgconfig    = foreign_config($gnupg_target);
1258    my $gpgpath      = quotemeta($gpgconfig{'gpg'} || "gpg");
1259    return $gpgpath;
1260
1261}
1262
1263sub switch_to_user
1264{
1265    if (!supports_users()) {
1266        return undef;
1267    }
1268    my ($username) = @_;
1269    my @uinfo = getpwnam($username);
1270    if (@uinfo) {
1271        switch_to_unix_user(\@uinfo);
1272    }
1273}
1274
1275sub is_linux
1276{
1277    return $gconfig{'os_type'} =~ /-linux$/;
1278}
1279
1280sub is_root
1281{
1282    return ($base_remote_user eq 'root' ? 1 : 0);
1283}
1284
1285sub get_env
1286{
1287    my ($key) = @_;
1288    return $ENV{ uc($key) };
1289}
1290
1291sub set_env
1292{
1293    my ($k, $v) = @_;
1294    $ENV{ uc($k) } = $v;
1295}
1296
12971;
1298