# Gossamer Threads Module Library - http://gossamer-threads.com/ # # GT::FileMan::Commands # CVS Info : 087,071,086,086,085 # $Id: Commands.pm,v 1.383 2008/11/27 07:06:56 brewt Exp $ # # Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved. # ==================================================================== # package GT::FileMan::Commands; use strict; use vars qw/%ICONS $READ_SIZE/; use GT::TempFile; use GT::Base qw/:persist/; use GT::AutoLoader; use GT::File::Tools qw/:all/; use GT::Date; use Cwd; use constants KB => 1024, MB => 1024 * 1024, FOLDER => 1, FILE => 2, SYMLINK => 3; %ICONS = ( 'gif jpg jpeg bmp' => ['image' => 'Image File'], 'txt' => ['txt' => 'Text File'], 'cgi pl pm' => ['perl' => 'Script File'], 'zip gz tar' => ['compressed' => 'Compressed File'], 'htm html shtm shtml' => ['html' => 'HTML File'], 'wav au mid mod mp3 wmv'=> ['audio' => 'Sound File'], 'exe' => ['exe' => 'Binary File'], 'doc' => ['doc' => 'MS Word'], 'xls' => ['xls' => 'MS Excel'], 'pdf' => ['pdf' => 'Adobe Acrobat'], 'unknown' => ['unknown' => 'Unknown'], 'folder' => ['folder' => 'File Folder'], 'symlink' => ['symlink' => 'Symlink'] ); # How large a chunk should we read into memory at once. $READ_SIZE = 500000; $COMPILE{home} = __LINE__ . <<'END_OF_SUB'; sub home { # ----------------------------------------------------------------------------- # Print out the home page # my ($self, %args) = @_; my $page = $self->{cgi}{page} || ''; if ($page =~ /^help(_\w*)?.html/) { return $self->print($page); } ($self->{cgi}{ajax} and $args{error} and lc $ENV{REQUEST_METHOD} eq 'post') ? $self->print_json_error($args{error}) : $self->print('home.html', \%args); } END_OF_SUB $COMPILE{password} = __LINE__ . <<'END_OF_SUB'; sub password { # ----------------------------------------------------------------------------- # Change password feature which is for single user version # my $self = shift; return $self->print_json({ html => $self->print('password.html', { json => 1 }) }, 1) if $self->{cgi}{form}; my $old = $self->{cgi}{old_password}; my $new = $self->{cgi}{new_password}; my $confirm = $self->{cgi}{confirm_password}; return $self->print_json_error($self->language('ERR_PASSWD_INPUT')) unless $old and $new and $confirm; return $self->print_json_error($self->language('ERR_INVALID_OLDPASSWD')) if $self->{cfg}{login}{password} ne crypt($old, $self->{cfg}{login}{password}) and $self->{cfg}{login}{password} ne GT::FileMan::encrypt($old, $self->{cfg}{login}{password}); return $self->print_json_error($self->language('ERR_PASSWD_NOMATCH')) if $new ne $confirm; $self->{cfg}{login}{password} = GT::FileMan::encrypt($new); $self->{cfg}->save(); $self->print_json(undef, 1, $self->language('MSG_PASSWD_UPDATED')); } END_OF_SUB $COMPILE{cd} = __LINE__ . <<'END_OF_SUB'; sub cd { # ----------------------------------------------------------------------------- # CD command. This must be a post request # my $self = shift; my $dname = $self->{cgi}{f}; my $path = $self->check_path($dname); return $self->print_json_error($path->{error}) if $path->{error}; return $self->print_json_error($self->language('ERR_NOT_FOUND', $dname)) unless $path->{exist}; $self->{cfg}{work_path} = $path->{work_path}; # Loading files my $files = $self->files; return $self->print_json_error($files) if ref $files ne 'HASH'; $self->print_json($files, 1); } sub files { # ----------------------------------------------------------------------------- # my $self = shift; my $fpath = $self->check_path(); return $self->language($fpath->{error}) if $fpath->{error}; my $path = $fpath->{full_path}; opendir (DIR, $path) or return $self->language('ERR_CANNOT_OPEN', $path, $!); my @rows = readdir(DIR); close DIR; my (@files, $readme); my $size = 0; foreach my $f (@rows) { next if ($f eq '.' or $f eq '..'); next if ($f =~ /^\./ and !$self->{default}{show_hidden}); my $file = $self->finfo("$path/$f", $f); if ($self->{cgi}{filter} == 1 and $file->[0] != FOLDER) { next; } elsif ($self->{cgi}{filter} == 2 and $file->[0] != FOLDER and !-T "$path/$f") { next; } push @files, $file; $size += $file->[3]; $readme = $f if lc $f eq 'readme'; } # loading current path and hidden objects my $hiddens = $self->hiddens(); my $paths = $self->current_path(); # Loading .htaccess and .htpassword my $htaccess_users = $self->load_htpasswd(); my $readme_content = ''; if ($self->{default}{readme} != 3 and $readme) { if (open(README, "< $path/$readme")) { my $count = 0; while () { chomp; next unless $_; $readme_content .= "$_\n"; $count++; last if $count == 10; } close README; } } return { hits => scalar @files, files => \@files, size => $size, paths => $paths, hiddens => $hiddens, root_path => $self->{session}{user} ? $self->{session}{user}{current}{disp} : undef, htaccess_users => $htaccess_users, readme_content => $self->{in}->html_escape($readme_content) }; } END_OF_SUB $COMPILE{search} = __LINE__ . <<'END_OF_SUB'; sub search { my $self = shift; return $self->print_json({ html => $self->print('search.html', { json => 1 }) }, 1) if $self->{cgi}{form}; my $result = $self->fsearch(); return $self->print_json_error($result->{error}) if $result->{error}; $self->flog("search|Search for: '$self->{cgi}{search_input}'"); $self->print_json($result, 1, $self->language('MSG_SEARCH', $result->{hits})); } sub fsearch { # ----------------------------------------------------------------------------- # my $self = shift; my $ftype = $self->{cgi}{search_type}; my $fmodified = $self->{cgi}{search_mod}; my $from_date = $self->{cgi}{fromdate}; my $to_date = $self->{cgi}{todate}; require GT::Date; if ($fmodified eq 'past_week') { $from_date = GT::Date::date_get(time - (7 * 86400), $self->{cfg}{date}{input}); $to_date = GT::Date::date_get(time, $self->{cfg}{date}{input}); } elsif ($fmodified eq 'past_month') { $from_date = GT::Date::date_get(time - (30 * 86400), $self->{cfg}{date}{input}); $to_date = GT::Date::date_get(time, $self->{cfg}{date}{input}); } elsif ($fmodified eq 'past_year') { $from_date = GT::Date::date_get(time - (365 * 86400)); $to_date = GT::Date::date_get(time, $self->{cfg}{date}{input}); } return { error => $self->language('ERR_SEARCH_COND') } unless $self->{cgi}{search_input} or $from_date or $to_date or $ftype; my (@files, @results); unless ($self->{cgi}{search_scope}) { my $fpath = $self->check_path(); unless ($fpath->{error}) { find($fpath->{full_path}, sub { push @files, shift }, { untaint => 1 }); } } else { my @selecteds = $self->{in}->param('cinput'); foreach (@selecteds) { my $fpath = $self->check_name($_); next if $fpath->{error}; if ($fpath->{isfile}) { push @files, $fpath->{full_path}; } else { find($fpath->{full_path}, sub { push @files, shift }, { untaint => 1 }); } } } my $src_term = $self->{cgi}{search_input} || ''; if (not $self->{cgi}{search_exp}) { $src_term = quotemeta($src_term); $src_term =~ s/\\\*/.*/g; $src_term =~ s/\\\?/./g; } $src_term = "(?i)$src_term" unless $self->{cgi}{search_case}; # Search file and directory names my $total_size = 0; unless ($self->{cgi}{search_content}) { $ftype =~ s/^\s+//; $ftype =~ s/\s+$//; my %ftype = map { $_ => 1 } split /\s*,\s*/, $ftype; foreach my $file (@files) { my ($fn) = $file =~ m,/([^/]+)$,; next if $fn eq $self->{work_path} or $fn !~ /$src_term/; if ($ftype) { my ($ext) = $fn =~ /\.([^.]+)$/; next unless $ext and $ftype{$ext}; } my $f = $self->finfo($file, $fn); next unless scalar @$f; # Search on modify date if ($fmodified) { my $modified = GT::Date::date_get($f->[5], $self->{cfg}{date}{input}); if ($from_date and $to_date) { next if GT::Date::date_is_smaller($modified, $from_date) or GT::Date::date_is_greater($modified, $to_date); } else { next if ($from_date and $modified ne $from_date) or ($to_date and $modified ne $to_date); } } push @results, $f; $total_size += $f->[3]; } } # Search file contents else { foreach my $file (@files) { # Only search the contents of files that are readable, have content and are non-binary next unless -f $file and -r _ and -s _ and -T _; my ($ext) = $file =~ /\.([^.]+)$/; my ($fn) = $file =~ m,/([^/]+)$,; # pdf files look like text files next if lc $ext eq 'pdf'; open(DATA, "<$file") or next; my ($buffer, $f, $bit); while (my $rs = read DATA, $buffer, $READ_SIZE) { $buffer = "$bit$buffer" if length $bit; $bit = ($rs == $READ_SIZE and $buffer =~ s/(?:\r|\r?\n)([^\r\n]+)$//) ? $1 : ''; if ($buffer =~ /$src_term/) { $f = $self->finfo($file, $fn); last; } } close DATA; next unless $f; push @results, $f; $total_size += $f->[3]; } } return { files => \@results, hits => scalar @results, size => $total_size }; } END_OF_SUB $COMPILE{replace} = __LINE__ . <<'END_OF_SUB'; sub replace { # ----------------------------------------------------------------------------- # Replace contents of text files # my $self = shift; return $self->print_json_error($self->language('ERR_SEARCH_COND')) unless $self->{cgi}{replace_input}; my (@files, @results); unless ($self->{cgi}{replace_scope}) { my $fpath = $self->check_path(); unless ($fpath->{error}) { find($fpath->{full_path}, sub { push @files, shift }, { untaint => 1 }); } } else { my @selecteds = $self->{in}->param('cinput'); foreach (@selecteds) { my $fpath = $self->check_name($_); next if $fpath->{error}; if ($fpath->{isfile}) { push @files, $fpath->{full_path}; } else { find($fpath->{full_path}, sub { push @files, shift }, { untaint => 1 }); } } } my $search_for = $self->{cgi}{replace_input} || ''; my $replace_with = $self->{cgi}{replace_with} || ''; if (not $self->{cgi}{replace_exp}) { $search_for = quotemeta($search_for); $search_for =~ s/\\\*/.*/g; $search_for =~ s/\\\?/./g; } $search_for = "\\b$search_for\\b" if $self->{cgi}{replace_wholeword}; $search_for = "(?i)$search_for" unless $self->{cgi}{replace_case}; my $total_size = 0; foreach my $file (@files) { # Only search the contents of files that are readable, have content and are non-binary next unless -f $file and -r _ and -s _ and -T _; my ($ext) = $file =~ /\.([^.]+)$/; my ($fn) = $file =~ m,/([^/]+)$,; next if lc $ext eq 'pdf'; open(DATA, "<$file") or next; my ($buffer, $f, $bit); while (my $rs = read DATA, $buffer, $READ_SIZE) { $buffer = "$bit$buffer" if length $bit; $bit = ($rs == $READ_SIZE and $buffer =~ s/(?:\r|\r?\n)([^\r\n]+)$//) ? $1 : ''; if ($buffer =~ /$search_for/) { $f++; last; } } close DATA; next unless $f; my $tempfile = new GT::TempFile; if (fcopy($file, "$$tempfile.tmp", $search_for, $replace_with)) { move($file, "$file.bak", { untaint => 1 }) if $self->{cgi}{replace_bak}; move("$$tempfile.tmp", $file, { untaint => 1 }); } push @results, $self->finfo($file, $fn); $total_size += -s $file; # Log the action $self->flog("replace|Replace '$search_for' with '$replace_with'"); } # loading current path and hidden objects my $hiddens = $self->hiddens(); my $paths = $self->current_path(); my $hits = scalar @results; $self->print_json({ hits => $hits, files => \@results, size => $total_size, hiddens => $hiddens, paths => $paths }, 1, $self->language('MSG_REPLACED', $hits)); } END_OF_SUB $COMPILE{command} = __LINE__ . <<'END_OF_SUB'; sub command { # ----------------------------------------------------------------------------- # Execute a command in shell # my $self = shift; # Untaint PATH $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; # Untaint the command my $command = $self->{cgi}{command_input} || ''; ($command) = $command =~ /^(.*)$/; # Remove serial file which is nolonger used my $serial = $self->{cgi}{serial}; ($serial) = $serial =~ /^(\w+)$/; if ($self->{cgi}{remove} and $serial) { unlink "$self->{cfg}{tmp_path}/$serial"; print $self->{in}->header(-type => 'text/xml; charset=utf8', '-no-cache' => 1); print qq!\n\ndone\n!; # Log the action $self->flog("command|$command"); return; } # Change to the working directory and then get the current path to ensure we # get a 'clean looking' path my $working_dir = $self->{cgi}{working_dir}; unless ($working_dir) { my $fpath = $self->check_path(); $working_dir = $fpath->{full_path} unless $fpath->{error}; } ($working_dir) = $working_dir =~ /^(.*)$/; chdir($working_dir); $working_dir = cwd(); my $prompt = $GT::FileMan::MSWIN ? "$working_dir>" : "[" . eval { getpwuid($<) . '@' } . "$ENV{SERVER_NAME} $working_dir]"; my $next_prompt = $prompt; return $self->print_json({ prompt => $prompt, working_dir => $working_dir }, 1) if $self->{cgi}{prompt} and !$self->{cgi}{retrieve}; return $self->print_json_error($self->language('ERR_COMMAND')) unless $command; my ($output, $error) = ('', ''); if ($self->{cgi}{retrieve} and $serial) { if (-f "$self->{cfg}{tmp_path}/$serial") { open(DATA, "< $self->{cfg}{tmp_path}/$serial"); read DATA, $output, -s DATA; close DATA; } # Getting the working directory if ($command =~ m/^\s*cd\s+(.+)/) { chdir($1); $working_dir = cwd(); $next_prompt = $GT::FileMan::MSWIN ? "$working_dir>" : "[" . eval { getpwuid($<) . '@' } . "$ENV{SERVER_NAME} $working_dir]"; } print $self->{in}->header(-type => 'text/xml; charset=utf8', '-no-cache' => 1); print qq~\n\n\n\n\n\n~; return; } # Run the command and send out a temporary file my $timeout = $self->{cfg}{command_timeout} || 60; my ($pid, $oldfh); $SIG{ALRM} = sub { kill('INT', $pid); die; }; alarm($timeout); eval { $pid = open(TMP, "$command |"); $oldfh = select(TMP); $| = 1; select($oldfh); while () { s/(\n|\r\n)$//; open DATA, ">> $self->{cfg}{tmp_path}/$serial" or die $!; print DATA $_. "\n"; close DATA; } close TMP; }; open DATA, ">> $self->{cfg}{tmp_path}/$serial" or die $!; print DATA ($error ? $error : 'done'); close DATA; } END_OF_SUB $COMPILE{upload} = __LINE__ . <<'END_OF_SUB'; sub upload { # ----------------------------------------------------------------------------- # Upload a file # my $self = shift; # Handle upload progress return $self->upload_progress() if $self->{cgi}{upload}; if ($self->{cgi}{num_files}) { my $num_files = $self->{cgi}{num_files}; my $uploaded = 0; my $declined = 0; my $total_size = 0; for (my $i = 1; $i <= $num_files; $i++) { next unless $self->{cgi}{"file-$i"}; my %result = $self->fupload($self->{cgi}{"file-$i"}, $self->{cgi}{"mode-$i"}); if ($result{error}) { return $self->print_json_error($result{error}) if $num_files == 1; $declined++; next; } $total_size += $result{size}; $uploaded++; $self->{diskspace}{free} -= $result{size} if $self->{diskspace}; # Log the action $self->flog("upload|$result{name}"); } return $self->print_json(undef, 1, $self->language('MSG_MULT_UPLOADED', $uploaded, friendly_size($total_size), $declined)); } else { my %result = $self->fupload($self->{cgi}{upload_input}, $self->{cgi}{upload_mode}); return $self->print_json_error($result{error}) if $result{error}; # Log the action $self->flog("upload|$result{name}"); return $self->print_json(undef, 1, $self->language('MSG_UPLOADED', $result{name}, friendly_size($result{size}))); } } sub fupload { # ----------------------------------------------------------------------------- # my ($self, $data, $mode) = @_; my $fn = $data; $fn =~ s/.*?([^\\\/:]+)$/$1/; $fn =~ s/[\[\]\s\$\#\%'"]/\_/g; unlink "$self->{cfg}{tmp_path}/$self->{serial}" if $self->{serial}; # Remove the log file return (error => $self->language('ERR_NOUPLOAD')) unless $fn; # Change the name if format if ($self->{cgi}{name_format} eq 'uc') { $fn =~ s/(\w+)/\U$1/gi; } elsif ($self->{cgi}{name_format} eq 'lc') { $fn =~ s/(\w+)/\L$1/gi; } my $file = $self->check_name($fn); return (error => $file->{error}) if $file->{error}; if ($file->{exist}) { return (error => $self->language('ERR_EXISTING', $fn)) unless $self->{cgi}{opt_overwrite}; return (error => $self->language('ERR_NOT_WRITABLE', $fn)) unless $file->{write}; } else { my $folder = $self->check_path(undef); return (error => $folder->{error}) if $folder->{error}; return (error => $self->language('ERR_NOT_FOUND', $self->{cfg}{work_path})) if $self->{cfg}{work_path} and !$folder->{exist}; return (error => $self->language('ERR_NOT_WRITABLE', $self->{cfg}{work_path})) unless $folder->{write}; } # Check free space if appicable. If diskspace is null, there is no limit return (error => $self->language('ERR_NOSPACE')) if $self->{diskspace} and !$self->{diskspace}{free}; my $full_path = $file->{full_path}; $mode ||= 'auto'; $mode = lc $mode; $mode = 'ascii' if $mode eq 'auto' and lc substr($full_path, -4, 4) ne '.pdf' and -T $full_path; my ($uploaded_size, $bytesread, $buffer) = (0, 0, ''); my $newlines = $GT::FileMan::MSWIN ? "\r\n" : "\n"; open(OUTFILE, "> $full_path"); binmode OUTFILE; while ($bytesread = read($data, $buffer, 1024)) { $buffer =~ s/$newlines/\n/g if $mode eq 'ascii'; print OUTFILE $buffer; $uploaded_size += $bytesread; } close OUTFILE; my $mod = $self->{cfg}{default}{upload_mode} || '644'; chmod(oct($mod), $full_path); my $fsize = -s $full_path; # Check free space if appicable. If diskspace is null, there is no limit if ($self->{diskspace} and $self->{diskspace}{free} < $fsize) { del($full_path, { untaint => 1 }); return $self->home(error => $self->language('ERR_NOSPACE')); } return (name => $file->{name}, size => $fsize); } END_OF_SUB $COMPILE{file} = __LINE__ . <<'END_OF_SUB'; sub file { # Create a new file # my $self = shift; if ($self->{cgi}{form}) { $self->print_json({ html => $self->print('editor.html', { json => 1, editor_mode => $self->{default}{editor_mode} ? 'html' : 'text' }) }, 1); } else { $self->edit(); } } END_OF_SUB $COMPILE{edit} = __LINE__ . <<'END_OF_SUB'; sub edit { # ----------------------------------------------------------------------------- # Edit a file # my $self = shift; # Print out editor form if ($self->{cgi}{form}) { my $fname = $self->{cgi}{f}; return $self->print_json_error($self->language('ERR_CANNOT_OPEN_MULT')) if ref $fname eq 'ARRAY'; my $file = $self->check_name($fname); return $self->print_json_error($file->{error}) if $file->{error}; return $self->print_json_error($self->language('ERR_NOT_FILE', $file->{name})) unless $file->{isfile}; return $self->print_json_error($self->language('ERR_NOT_READABLE', $file->{name})) unless $file->{read}; return $self->open_compressed($file) if $file->{type} eq 'compress'; return $self->print_json({ file => $file }) unless $file->{type} =~ /text|html/; # Get content of the file open (DATA, "<$file->{full_path}") or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $file->{name}, $!)); read (DATA, my $content, -s DATA); close DATA; if ($file->{type} eq 'html') { $file->{content} = $content; } else { $content =~ s,\r\n,\n,g; $file->{content} = $content; } return $self->print_json({ html => $self->print('editor.html', { file => $file, editor_mode => ($file->{type} eq 'html' and $self->{default}{editor_mode}) ? 'html' : 'text', json => 1 }), type => $file->{type}, }, 1); } # Save file my $old = $self->{cgi}{file}; my $content = $self->{cgi}{editor}; my $mode = $self->{cgi}{editor_mode}; return $self->print_json_error($self->language('ERR_NOSPACE')) if ($self->{diskspace} and (!$self->{diskspace}{free} or $self->{diskspace}{free} < length $content)); # Save content into a new file my ($history, $file); my $fname = $self->{cgi}{edit_input} || $self->{cgi}{file_input}; if ($fname and $fname ne $old) { $file = $self->check_name($fname); return $self->print_json_error($file->{error}) if $file->{error}; return $self->print_json_error({ file => $file, confirm => 1 }) if $file->{exist} and !$self->{cgi}{overwrite_confirmed}; $history = "newfile|$self->{cgi}{name}"; } # Update file with new content else { $file = $self->check_name($old); return $self->print_json_error($file->{error}) if $file->{error}; return $self->print_json_error($self->language('ERR_NOT_WRITEABLE', $file->{name})) unless $file->{write}; $history = "edit|$old"; } open(DATA, "> $file->{full_path}") or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $file->{name}, $!)); print DATA $content; close DATA; # Log the action $self->flog($history); return $self->print_json(undef, 1, $self->language('MSG_FILE_SAVED', $file->{name})); } END_OF_SUB $COMPILE{makedir} = __LINE__ . <<'END_OF_SUB'; sub makedir { # ----------------------------------------------------------------------------- # Create a new directory # my $self = shift; my $dname = $self->{cgi}{makedir_input}; return $self->print_json_error($self->language('ERR_INVALID_INPUT')) unless $dname; my $new = $self->check_name($dname); return $self->print_json_error($new->{error}) if $new->{error}; return $self->print_json_error($self->language('ERR_EXISTING', $dname)) if $new->{exist}; return $self->print_json_error($self->language('ERR_NOSPACE')) if $self->{diskspace} and !$self->{diskspace}{free}; rmkdir($new->{full_path}, 0755, { untaint => 1 }) or return $self->print_json_error($self->language('ERR_MAKDEDIR', $dname, $!)); $self->flog("makedir|$dname"); return $self->print_json(undef, 1, $self->language('MSG_MAKEDIR', $dname)); } END_OF_SUB $COMPILE{chmod} = __LINE__ . <<'END_OF_SUB'; sub chmod { # ----------------------------------------------------------------------------- # Change the permissions of a file # my $self = shift; # Change mod my @files = $self->{in}->param('cinput'); my $mod = $self->{cgi}{chmod_input}; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless scalar @files; return $self->print_json_error($self->language('ERR_CHMOD_INPUT')) unless $mod; # Untaint permission input ($mod) = $mod =~ /^([0-7]{3,4})$/; return $self->print_json_error($self->language('ERR_INVALID_PERM')) unless $mod; my $changed = 0; my $oct_mod = oct($mod); foreach my $f (@files) { my $file = $self->check_name($f); if ($file->{error}) { return $self->print_json_error($file->{error}) if scalar @files == 1; # Return if error occurs for a single file next; } if (-d $file->{full_path} and $self->{cgi}{chmod_recursive}) { find($file->{full_path}, sub { chmod($oct_mod, shift) }, { untaint => 1 }); $changed++; } else { chmod($oct_mod, $file->{full_path}) and $changed++; } } return $self->print_json_error('cannot_chmod') unless $changed; # Log the action $self->flog("chmod|" . join(', ', @files)); $self->print_json(undef, 1, scalar @files == 1 ? $self->language('MSG_CHMODED', $files[0]) : $self->language('MSG_MULT_CHMODED', $changed)); } END_OF_SUB $COMPILE{protect} = __LINE__ . <<'END_OF_SUB'; sub protect { # ----------------------------------------------------------------------------- # Protect command: create .htaccess file # my $self = shift; my $pwd_path = $self->{default}{pwd_path}; my ($htpwd, $htacc, $name_pwd, $name_acc); # Create the .htpasswd if ($pwd_path) { my $path = $self->check_path($pwd_path); if (!$path->{error} and $path->{exist}) { my $current = $self->check_path(); unless ($current->{error}) { $current->{full_path} =~ s/[\/ \:]/\_/g; $htpwd = "$path->{full_path}/.htpass$current->{full_path}"; ($htpwd) = $htpwd =~ /^(.*)$/; # Untaint the path } } } unless ($htpwd) { my $fpassword = $self->check_name('.htpasswd'); return $self->print_json_error($fpassword->{error}) if $fpassword->{error}; $htpwd = $fpassword->{full_path}; } ($name_pwd) = $htpwd =~ /^$self->{cfg}{root_path}\/(.*)/; unless (-e $htpwd) { open (FILE, "> $htpwd") or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $name_pwd, $!)); close FILE; } # Create the .htaccess my $faccess = $self->check_name('.htaccess'); return $self->print_json_error($faccess->{error}) if $faccess->{error}; $htacc = $faccess->{full_path}; ($name_acc) = $htacc =~ /^$self->{cfg}{root_path}\/(.*)/; unless ($faccess->{exist} and $faccess->{size}) { my $error = $self->create_htaccess($htacc, $htpwd); return $self->print_json_error($error) if $error; } return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $name_pwd)) unless -w $htpwd; return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $name_acc)) unless -w $htacc; # Overwrite existing .htaccess if AuthUserFile isn't matching with htpwd open (HTACC, "< $htacc") or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $name_acc, $!)); my @info = ; close HTACC; my $found; LINE: foreach (@info) { if ( $_ =~ /$htpwd/ ) { $found = 1; last; } } unless ($found) { my $error = $self->create_htaccess($htacc, $htpwd); return $self->print_json_error($error) if $error; } # Delete all users if ($self->{cgi}{'protect-delete-all'}) { unlink $htpwd; unlink $htacc; return $self->print_json(undef, 1, $self->language('MSG_MULTUSER_DELETED', 'All')); } # Create/delete a user my (@users, $msg); my $username = $self->{cgi}{protect_username}; my $password = $self->{cgi}{protect_password}; my $del_user = $self->{cgi}{protect_user}; if (-s $htpwd) { open (HTPWD, "< $htpwd") or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $name_pwd, $!)); @users = grep { $_ !~ /^$del_user:/} ; close HTPWD; } my $message; if ($username and $password) { return $self->print_json_error($self->language('ERR_UID_INVALID', $username)) if $username =~ /:/; foreach my $i (0 .. @users) { my $u = $users[$i]; if ($u =~ /^$username:/) { return $self->print_json_error($self->language('ERR_UID_EXISTING', $username)) unless $self->{cgi}{opt_protect_overwrite}; delete $users[$i]; } } my $crypted; if ($self->{cgi}{opt_protect_apache}) { require GT::MD5::Crypt; my $salt = join '', ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/')[map rand 64, 1 .. 8]; $crypted = GT::MD5::Crypt::apache_md5_crypt($password, $salt); } else { my @salt_chars = ('A' .. 'Z', 0 .. 9, 'a' .. 'z', '.', '/'); my $salt = join '', @salt_chars[rand 64, rand 64]; $crypted = crypt($password, $salt); } push @users, "$username:$crypted\n"; $message = $self->language('MSG_USER_ADDED', $username); } elsif ($self->{cgi}{protect} and !$del_user) { return $self->print_json_error($self->language('ERR_PROTECT_REQUIRED')); } elsif ($del_user) { $message = $self->language('MSG_USER_DELETED', $del_user); } if (@users) { open (HTPWD, "> $htpwd") or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $name_pwd, $!)); print HTPWD join("", @users); close HTPWD; foreach (@users) { my ($u, $p) = split(/:/, $_); } } else { unlink $htpwd; unlink $htacc; } return $self->print_json(undef, 1, $message); } END_OF_SUB $COMPILE{setup} = __LINE__ . <<'END_OF_SUB'; sub setup { # ----------------------------------------------------------------------------- # Change configurations # my $self = shift; return $self->print_json({ html => $self->print('setup.html', { json => 1 }) }, 1) if $self->{cgi}{form}; my %required_fields = (cgi_url => 1, static_url => 1, root_path => 1); foreach (keys %required_fields) { return $self->print_json_error($self->language('MSG_REQUIRED', $_)) unless $self->{cgi}{$_}; } if ($self->{cfg}{fversion} eq 'multiple') { return $self->print_json_error($self->language('ERR_MAIL_SERVER')) unless $self->{cgi}{email_smtp_server} or $self->{cgi}{email_mail_path}; return $self->print_json_error($self->language('ERR_INVALID_MAIL')) if $self->{cgi}{email_smtp_server} and $self->{cgi}{email_mail_path}; } my $config = $self->{cfg}; foreach my $k (keys %$config) { if (ref $config->{$k} eq 'HASH') { my $hsh = $config->{$k}; $config->{$k} = $self->fsetup($hsh, $k); } elsif (!defined $self->{in}->param($k)) { next; } else { $config->{$k} = $self->{in}->param($k); } } # Overwrite current configurations $self->{cfg} = $config; $self->{cfg}->save(); # Log the action $self->flog("setup|Change the configuration"); $self->print_json(undef, 1, $self->language('MSG_CHANCES_SAVED')); } sub fsetup { # ----------------------------------------------------------------------------- # my ($self, $cfg, $key) = @_; foreach my $k (keys %$cfg) { my $val = $cfg->{$k}; if (ref $val eq 'HASH') { $cfg->{$k} = $self->fsetup($val, "${key}_$k"); } elsif (defined $self->{in}->param("${key}_$k")) { $cfg->{$k} = $self->{in}->param("${key}_$k") || ''; } } return $cfg; } END_OF_SUB $COMPILE{preferences} = __LINE__ . <<'END_OF_SUB'; sub preferences { # ----------------------------------------------------------------------------- # Update preferences # my $self = shift; return $self->print_json({ html => $self->print('preferences.html', { json => 1 }) }, 1) if $self->{cgi}{form}; my ($pwd_path, $default_path) = ('', ''); if ($self->{cgi}{pwd_path}) { my $path = $self->check_path($self->{cgi}{pwd_path}); return $self->print_json_error($path->{error}) if $path->{error}; return $self->print_json_error($self->language('ERR_NOT_FOUND', $self->{cgi}{pwd_path})) unless $path->{exist}; return $self->print_json_error($self->lanugage('ERR_NOT_FOLDER', $self->{cgi}{pwd_path})) unless $path->{isdir}; $pwd_path = $path->{full_path}; } if ($self->{cgi}{path}) { my $path = $self->check_path($self->{cgi}{path}); return $self->print_json_error($path->{error}) if $path->{error}; return $self->print_json_error($self->language('ERR_NOT_FOUND', $self->{cgi}{path})) unless $path->{exist}; return $self->print_json_error($self->language('ERR_NOT_FOLDER', $self->{cgi}{path})) unless $path->{isdir}; $default_path = $path->{full_path}; } my %defaults = ( pwd_path => $pwd_path, path => $default_path, maxhits => $self->{cgi}{maxhits} || 25, sb => $self->{cgi}{sb} || 'name', so => $self->{cgi}{so} || 'asc', readme => $self->{cgi}{readme} || 1, show_hidden => $self->{cgi}{show_hidden} || 0, editor_mode => $self->{cgi}{editor_mode} || 0, effect => $self->{cgi}{effect} || 1 ); my $defaults = join ';', map "$_=$defaults{$_}" , (keys %defaults); print $self->{in}->header( -cookie => [ $self->{in}->cookie(-name => 'fileman_defaults', -value => $defaults, -expires => '+5y') ] ); $self->{default} = $self->default(%defaults); $self->print_json({ defaults => $self->{default} }, 1, $self->language('MSG_CHANCES_SAVED')); } END_OF_SUB $COMPILE{download} = __LINE__ . <<'END_OF_SUB'; sub download { # ----------------------------------------------------------------------------- # Download command # my $self = shift; my @files = $self->{in}->param('cinput'); my $opt_zip = $self->{cgi}{download_compress}; my $opt_mode = $self->{cgi}{download_mode}; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless scalar @files; # Download a single file if (scalar @files == 1 and !$opt_zip) { my $file = $self->check_name($files[0]); return $self->print_json_error($file->{error}) if $file->{error}; return $self->print_json_error($self->language('ERR_NOT_READABLE', $file->{name})) unless $file->{read}; if ($file->{isfile}) { $self->flog("download|$file->{name}"); return $self->print_json({ file => $file, mode => $opt_mode }, 1); } else { $opt_zip = 1; } } # Download multiple files require GT::TempFile; my $tempfile = new GT::TempFile(tmp_dir => $self->{cfg}{tmp_path}); my $ext = 'tar'; if ($opt_zip == 3 and $GT::FileMan::HAVE_AZIP) { $ext = 'zip'; $self->create_zip(\@files, "$$tempfile.$ext"); } else { $ext = ($opt_zip == 2 and $GT::FileMan::HAVE_GZIP) ? 'tar.gz' : 'tar'; $self->create_tar(\@files, "$$tempfile.$ext"); } $self->flog("download|$$tempfile.$ext"); my $filename = "$$tempfile.$ext"; $filename =~ s,^$self->{cfg}{tmp_path}/,,; return $self->print_json({ file => { name => $filename, source => $ext, mode => 'binary' }, }, 1); } END_OF_SUB $COMPILE{cmdcopy} = __LINE__ . <<'END_OF_SUB'; sub cmdcopy { # ----------------------------------------------------------------------------- # Copy command # my ($self, $action, $to) = @_; $to ||= $self->{cgi}{copy_input}; my @files = $self->{in}->param('cinput'); my $copied = $self->{cgi}{num_done} || 0; return $self->print_json_error($self->language('ERR_INVALID_PATH')) unless $to; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless scalar @files or $self->{cgi}{'confirm-name'}; my $path = $self->check_path($to); return $self->print_json_error($path->{error}) if $path->{error}; if ($path->{exist}) { return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $to)) unless $path->{write}; } else { my ($fname) = $path->{full_path} =~ /\/([^\/]+)$/; (my $path_to = $path->{full_path}) =~ s/\/$fname$//; return $self->print_json_error($self->language('ERR_NOT_FOUND', $to)) unless -e $path_to; return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $to)) unless -w $path_to; } if ($self->{diskspace} and !$action) { # check free space if appicable my $need_space = $self->size(\@files); return $self->print_json_error($self->language('ERR_NOSPACE')) unless ($self->{diskspace}{free} or $self->{diskspace}{free} > $need_space); } # Need a confirmation on overwrite existing files my @loop_files; if ($self->{cgi}{'confirm-name'}) { return $self->print_json(undef, 1, $self->language('MSG_COPIED', $copied, $to)) if $self->{cgi}{'button-cancel'}; if ($self->{cgi}{'confirm-name'} eq 'copy-all') { foreach my $f (@files) { next unless $f; my $file = $self->check_name($f); next if $file->{error}; if ($action eq 'move') { move($file->{full_path}, $path->{full_path}, { untaint => 1 }) and $copied++; } else { copy($file->{full_path}, $path->{full_path}, { untaint => 1 }) and $copied++; } } } elsif ($self->{cgi}{'confirm-name'} !~ /button-cancel|button-skip/) { my $fcurrent = $self->{cgi}{'confirm-name'}; my $file = $self->check_name($fcurrent); unless ($file->{error}) { if ($action eq 'move') { move($file->{full_path}, $path->{full_path}, { untaint => 1 }) and $copied++; } else { copy($file->{full_path}, $path->{full_path}, { untaint => 1 }) and $copied++; } } foreach my $f (@files) { next unless $f and $f ne $fcurrent; push @loop_files, $f; } } } else { foreach my $f (@files) { my $file = $self->check_name($f); next if $file->{error} or $file->{full_path} eq $path->{full_path}; if (-e "$path->{full_path}/$f") { push @loop_files, $f; next; } if ($action eq 'move') { move($file->{full_path}, $path->{full_path}, { untaint => 1 }) and $copied++; } else { copy($file->{full_path}, $path->{full_path}, { untaint => 1 }) and $copied++; } } } # Need confirmation on overwrite existing files return $self->print_json({ confirms => \@loop_files, num_done => $copied, input => $to }, 1) if scalar @loop_files; my $history = $action eq 'move' ? "move" : "copy"; my $msg = $action eq 'move' ? $self->language('MSG_MOVED', $copied, $to) : $self->language('MSG_COPIED', $copied, $to); $self->flog("$history|To: $path->{full_path} ($copied files)"); return $self->print_json(undef, 1, $msg); } END_OF_SUB $COMPILE{cmdmove} = __LINE__ . <<'END_OF_SUB'; sub cmdmove { # ----------------------------------------------------------------------------- # Move command # my $self = shift; return $self->cmdcopy('move', $self->{cgi}{'move_input'}); } END_OF_SUB $COMPILE{rename} = __LINE__ . <<'END_OF_SUB'; sub rename { # ----------------------------------------------------------------------------- # Rename command # my $self = shift; my $to = $self->{cgi}{rename_input}; my $f = $self->{cgi}{cinput}; return $self->print_json_error($self->language('ERR_RENAME_INPUT')) unless $to; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless $f; return $self->print_json_error($self->language('ERR_CANNOT_RENAME', $to)) if $to =~ /\//; return $self->print_json_error($self->language('ERR_MULT_SELECTED')) if ref $f eq 'ARRAY'; my $path = $self->check_path($to); return $self->print_json_error($path->{error}) if $path->{error}; return $self->print_json({ file => { name => $to }, confirm => 1 }, 1) if $path->{exist} and !$self->{cgi}{overwrite_confirmed}; my ($fname) = $path->{full_path} =~ /\/([^\/]+)$/; (my $path_to = $path->{full_path}) =~ s/\/$fname$//; return $self->print_json_error($self->language('ERR_NOT_FOUND', $to)) unless -e $path_to; return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $self->{cfg}{work_path})) unless -w $path_to; my $file = $self->check_name($f); return $self->print_json_error($file->{error}) if $file->{error}; move($file->{full_path}, $path->{full_path}, { untaint => 1 }); $self->flog("Rename|From: $f - To:$fname"); $self->print_json(undef, 1, $self->language('MSG_RENAMED', $f, $fname)); } END_OF_SUB $COMPILE{delete} = __LINE__ . <<'END_OF_SUB'; sub delete { # ----------------------------------------------------------------------------- # Delete files & directories # my $self = shift; my @files = $self->{in}->param('cinput'); return $self->print_json_error($self->language('ERR_NOSELECTED')) unless scalar @files or $self->{cgi}{'confirm-name'}; my $num_done = $self->{cgi}{num_done} || 0; my $confirmed = $self->{cgi}{'confirm-name'} || ''; my (@deleted, @loop_files); foreach my $f (@files) { my $file = $self->check_name($f); next if $file->{error} or !$file->{exist}; # Delete confirmed files and folders if ($confirmed eq 'delete-all' or $confirmed eq $f) { my $deleted = 0; if ($file->{isfile}) { del($file->{full_path}, { untaint => 1 }) and $deleted = 1; } else { deldir($file->{full_path}, { untaint => 1 }) and $deleted = 1; } if ($deleted) { $num_done++; push @deleted, $file->{full_path}; } } elsif (!$confirmed or $confirmed !~ /button-skip|button-cancel/) { push @loop_files, $f; } } # Log deleted files $self->flog("delete|".join("; ", @deleted)) if scalar @deleted; # Need to confirm on deleting selected files return $self->print_json({ confirms => \@loop_files, num_done => $num_done }, 1) if scalar @loop_files; return $self->print_json(undef, 1, $self->language($num_done > 1 ? 'MSG_MULT_DELETED' : 'MSG_DELETED', $num_done)); } END_OF_SUB $COMPILE{symlink} = __LINE__ . <<'END_OF_SUB'; sub symlink { # ----------------------------------------------------------------------------- # Symlink command # my $self = shift; my @files = $self->{in}->param('cinput'); my $to = $self->{cgi}{symlink_input}; return $self->print_json_error($self->language('ERR_SYMLINK_INPUT')) unless $to; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless scalar @files; my $path = $self->check_path($to); return $self->print_json_error($path->{error}) if $path->{error}; return $self->print_json_error($self->language('ERR_NOT_FOUND', $to)) unless $path->{exist}; return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $to)) unless $path->{write}; my $links = 0; foreach my $f (@files) { my $file = $self->check_name($f, 1); next if $file->{error} or $file->{full_path} eq $path->{full_path}; # Untaint the path my $p = "$path->{full_path}/$f"; ($p) = $p =~ /^(.*)$/; symlink($file->{full_path}, $p) and $links++; } return $self->print_json(undef, 1, $self->language('MSG_LINKED', $links, $to)); } END_OF_SUB $COMPILE{compress} = __LINE__ . <<'END_OF_SUB'; sub compress { # ----------------------------------------------------------------------------- # Compress files and directories # my $self = shift; my @files = $self->{in}->param('cinput'); my $loc = $self->{cgi}{compress_input}; my $mode = $self->{cgi}{compress_mode} || 1; return $self->print_json_error($self->language('ERR_GZIP_REQUIRED')) if $mode == 2 and !$GT::FileMan::HAVE_GZIP; return $self->print_json_error($self->language('ERR_AZIP_REQUIRED')) if $mode == 3 and !$GT::FileMan::HAVE_AZIP; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless scalar @files; # Verify file name as well as location to store the compressed file my $fname; unless ($loc) { my $path = $self->check_path(undef); return $self->print_json_error($path->{error}) if $path->{error}; return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $path->{full_path})) unless $path->{write}; return $self->print_json_error($self->language('ERR_COMPRESS_INPUT')) if scalar @files > 1; ($fname) = $files[0] =~ /([^.]+)/; return $self->print_json_error($self->language('ERR_COMPRESS_INPUT')) unless $fname; } elsif ($loc =~ /\/$/) { return $self->print_json_error($self->language('ERR_COMPRESS_INPUT')) if scalar @files > 1; ($fname) = $files[0] =~ /([^.]+)/; return $self->print_json_error($self->language('ERR_COMPRESS_INPUT')) unless $fname; $fname = "$loc$fname"; } else { $fname = $loc; } # Handling compressed file extension ($fname) = $fname =~ /([^.]+)/; if ($mode > 1) { $fname .= $mode == 2 ? '.tar.gz' : '.zip'; } else { $fname .= '.tar'; } my $file = $self->check_name($fname); return $self->print_json_error($file->{error}) if $file->{error}; return $self->print_json_error($self->language('ERR_NOSPACE')) if $self->{diskspace} and !$self->{diskspace}{free}; return $self->print_json_error($self->language('ERR_FILENAME_EXISTING')) if scalar @files == 1 and $files[0] eq $fname; return $self->print_json_error({ file => $file, confirm => 1 }) if $file->{exist} and !$self->{cgi}{overwrite_confirmed}; if ($mode == 3) { $self->create_zip(\@files, $file->{full_path}); } else { $self->create_tar(\@files, $file->{full_path}); } if ($self->{diskspace} and $self->{diskspace}{free} < -s $file->{full_path}) { unlink $file->{full_path}; return $self->print_json_error('nospace'); } $self->flog("compress|$file->{name}"); return $self->print_json(undef, 1, $self->language('MSG_COMPRESSED', $file->{name})); } END_OF_SUB $COMPILE{uncompress} = __LINE__ . <<'END_OF_SUB'; sub uncompress { # ----------------------------------------------------------------------------- # uncompress a file # my $self = shift; my @files = $self->{in}->param('compress'); my $to = $self->{cgi}{uncompress_input}; my $scope = $self->{cgi}{uncompress_scope}; my $fname = $self->{cgi}{compressed_file}; my $file = $self->check_name($fname); my $path = $self->check_path($to); return $self->print_json_error($file->{error} || $path->{error}) if $file->{error} or $path->{error}; return $self->print_json_error($self->language('ERR_NOSELECTED')) if $scope and !scalar @files; return $self->print_json_error($self->language('ERR_NOT_FOUND', $fname)) unless $file->{exist}; return $self->print_json_error($self->language('ERR_NOT_READABLE', $fname)) unless $file->{read}; return $self->print_json_error($self->language('ERR_NOT_WRITABLE', $to)) if $path->{exist} and !$path->{write}; return $self->print_json_error($self->language('ERR_NOSPACE')) if ($self->{diskspace} and !$self->{diskspace}{free}); # Untaint the path ($path->{full_path}) = $path->{full_path} =~ /^(.*)$/; # Create the target folder if it does not exist unless ($path->{exist}) { rmkdir($path->{full_path}, 0755, { untaint => 1 }) or return $self->print_json_error($self->language('ERR_MAKEDIR', $to, $!)); } my ($copied, $total_size, $hits) = (0, 0, 0); my %error; if ($file->{full_path} =~ /.zip$/i) { require Archive::Zip; my $zip = Archive::Zip->new($file->{full_path}) or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $fname, $!)); my @members = $zip->members(); $hits = scalar @members; foreach my $f (@members) { last if ($self->{diskspace} and $total_size + $f->uncompressedSize > $self->{diskspace}{free}); my $name = $f->fileName; my $found = ($scope and scalar @files) ? 0 : 1; if ($scope) { foreach (@files) { next if $_ ne $name; $found = 1; last; } } next unless $found; $copied++; $total_size += $f->uncompressedSize; # Untaint file name ($name) = $name =~ /^(.*)$/; $zip->extractMember($name, "$path->{full_path}/$name"); } } else { require GT::Tar; my $tar = GT::Tar->open($file->{full_path}) or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $fname, $GT::Tar::error)); my $cfiles = $tar->files; $hits = scalar @$cfiles; foreach my $f (@$cfiles) { last if ($self->{diskspace} and $total_size + $f->{size} > $self->{diskspace}{free}); my $name = $f->{name}; my $found = ($scope and scalar @files) ? 0 : 1; if ($scope) { foreach (@files) { next if $_ ne $name; $found = 1; last; } } next unless $found; $total_size += $f->{size}; $copied++; $f->write($path->{full_path}, { untaint => 1 }); } } $self->flog("cmd_uncompress|$fname"); $self->print_json(undef, 1, $self->language('MSG_UNCOMPRESSED', $fname, $hits, $copied)); } END_OF_SUB $COMPILE{tail} = __LINE__ . <<'END_OF_SUB'; sub tail { # ----------------------------------------------------------------------------- # tail command # my $self = shift; my $lines = $self->{cgi}{tail_input} || 25; my $retime = $self->{cgi}{tail_retime}; my $fname = $self->{cgi}{cinput}; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless $fname; return $self->print_json_error($self->language('ERR_MULT_SELECTED')) if ref $fname eq 'ARRAY'; my $file = $self->check_name($fname); return $self->print_json_error($file->{error}) if $file->{error}; return $self->print_json_error($self->language('ERR_NOTTEXT_FILE', $fname)) unless $file->{isfile} and $file->{text}; return $self->print_json_error($self->language('ERR_READABLE', $fname)) unless $file->{read}; return $self->print_json_error($self->language('ERR_EMPTY_FILE', $fname)) unless $file->{size}; my ($follow, $buffer, $content); @ARGV = grep { if ($_ eq "-f") { $follow++; 0 } else { 1 } } @ARGV; open DATA, "<$file->{full_path}" or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $fname, $!)); my $read_size = $file->{size} > $READ_SIZE ? $READ_SIZE : $file->{size}; seek DATA, -$read_size, 2; read DATA, $buffer, $read_size; my $read = $read_size; $lines--; while () { if ($buffer =~ /\n(.*(?:\n.*){$lines}\n?$)/) { $content .= $1; last; } $read_size = ($file->{size} - $read > $READ_SIZE) ? $READ_SIZE : $file->{size} - $read; unless ($read_size == 0) { $content .= $buffer; last; } seek DATA, -($read_size + $read), 2; $read += $read_size; my $new_buffer; my $bytes_read = read DATA, $new_buffer, $read_size; if ($bytes_read == 0) { $content .= $buffer; last; } $buffer = $new_buffer . $buffer; } my $count = 0; if ($follow) { seek DATA, 0, 2; # Seek to the end of the file while () { select undef, undef, undef, 1; seek DATA, 0, 1 or last; # Reset eof(FILE) print while ; seek DATA, 0, 2; last if ($count++ > 60); # Only run for one min max. } } $self->print_json({ output => $self->{in}->html_escape($content), refresh => $retime }, 1); } END_OF_SUB $COMPILE{perl} = __LINE__ . <<'END_OF_SUB'; sub perl { # ----------------------------------------------------------------------------- # Perl command # my $self = shift; my @files = $self->{in}->param('cinput'); return $self->print_json_error($self->language('ERR_PERL_SELECTED')) unless @files; $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin'; #for taint mode my @output; foreach my $f (@files) { my $file = $self->check_name($f); if ($file->{error}) { push @output, { name => $f, error => $file->{error} }; next; } elsif (!$file->{read}) { push @output, { name => $f, error => $self->language('ERR_NOT_READABLE', $file->{full_path}) }; next; } elsif (!$file->{isfile} or !$file->{text}) { push @output, { name => $f, error => $self->language('ERR_NOTPERL_FILE', $file->{full_path}) }; next; } my ($ext) = $f =~ /\.([^.]+)$/; if ($ext !~ /^(?:cgi|pl|pm)$/i) { push @output, { name => $f, error => $self->language('ERR_NOTPERL_FILE', $file->{full_path}) }; next; } my ($fname) = $file->{full_path} =~ /^$self->{cfg}{root_path}(.*)/; my $result = fsystem($self->{cfg}{path_to_perl}, '-cw -I', "$self->{cfg}{private_path}/lib", $file->{full_path}, ); push @output, { name => $f, %$result }; } $self->print_json({ files => \@output }, 1); } END_OF_SUB $COMPILE{diff} = __LINE__ . <<'END_OF_SUB'; sub diff { # ----------------------------------------------------------------------------- # Diff Command # my $self = shift; my $fname1 = $self->{cgi}{cinput}; my $fname2 = $self->{cgi}{diff_input}; return $self->print_json_error($self->language('ERR_NOSELECTED')) unless $fname1; return $self->print_json_error($self->language('ERR_DIFF_INPUT')) unless $fname2; return $self->print_json_error($self->language('ERR_MULT_SELECTED')) if ref $fname1 eq 'ARRAY'; my $file1 = $self->check_name($fname1); my $file2 = $self->check_name($fname2); return $self->print_json_error($file1->{error} || $file2->{error}) if $file1->{error} or $file2->{error}; return $self->print_json_error($self->language('ERR_NOT_FOUND', $fname1)) unless $file1->{exist}; return $self->print_json_error($self->language('ERR_NOT_FOUND', $fname2)) unless $file2->{exist}; return $self->print_json_error($self->language('ERR_NOTTEXT_FILE', $fname1)) unless $file1->{text}; return $self->print_json_error($self->language('ERR_NOTTEXT_FILE', $fname2)) unless $file2->{text}; require GT::FileMan::Diff; my $diff = GT::FileMan::Diff::html_diff($file1->{full_path}, $file2->{full_path}, 3); return $self->print_json_error($self->language('ERR_CANNOT_OPEN', ($diff == 1 ? $fname1 : $fname2), $!)) unless ref $diff; $self->print_json({ output => $$diff }, 1); } END_OF_SUB $COMPILE{preview} = __LINE__ . <<'END_OF_SUB'; sub preview { my $self = shift; my $file = $self->check_name($self->{cgi}{f}); return $self->print_json_error($self->language('ERR_INVALID_FILE')) if $file->{error}; return $self->print_json_error($self->language('ERR_NOT_FILE', $file->{name})) unless $file->{isfile}; return $self->print_json_error($self->language('ERR_NOT_READABLE', $file->{name})) unless $file->{read}; if ($file->{type} =~ /^(?:text|html)$/) { open(TEXT, $file->{full_path}) or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $file->{name}, $!)); read TEXT, my $content, -s TEXT; close TEXT; $file->{content} = $self->{in}->html_escape($content); } elsif ($file->{type} eq 'compress') { return $self->open_compressed($file); } return $self->print_json({ file => $file }, 1); } END_OF_SUB $COMPILE{fdownload} = __LINE__ . <<'END_OF_SUB'; sub fdownload { # ----------------------------------------------------------------------------- # my $self = shift; my $fname = $self->{cgi}{f}; my $mode = $self->{cgi}{mode} || 'auto'; return if ref $fname eq 'ARRAY'; my $file; if ($self->{cgi}{type}) { # Untaint the path my $full_path = "$self->{cfg}{tmp_path}/$fname"; ($full_path) = $full_path =~ /^(.*)$/; $file = { full_path => $full_path, name => 'download.' . $self->{cgi}{type}, size => -s $full_path } } else { $file = $self->check_name($fname); return if $file->{error} or !$file->{isfile}; } $mode = 'ascii' if ($mode and lc $mode eq 'auto' and $file->{text}); open(DATA, $file->{full_path}) or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $file->{name}, $!)); if ($file->{name} =~ /.zip$/i) { print $self->{in}->header( '-type' => 'application/octect-stream', '-Content-Length' => $file->{size}, '-Content-Transfer-Encoding' => 'binary', '-Content-Disposition' => \(qq/attachment; filename="$file->{name}"/ . (defined($file->{size}) ? "; size=$file->{size}" : '')) ); } else { print $self->{in}->header($self->{in}->file_headers( filename => $file->{name}, inline => 0, size => $file->{size} )); } binmode STDOUT if $GT::FileMan::MSWIN; binmode DATA; my $newlines = $GT::FileMan::MSWIN ? "\r\n" : "\n"; my $buffer; while (read(DATA, $buffer, $READ_SIZE)){ $buffer =~ s,$newlines,\n,g if $mode eq 'ascii'; print $buffer; } close DATA; } END_OF_SUB $COMPILE{env} = __LINE__ . <<'END_OF_SUB'; sub env { # ----------------------------------------------------------------------------- # Print out environment # my $self = shift; my $env = GT::FileMan::base_env($self->{in}, $self->{cfg}{version}, $self->{commands}); $self->print_json({ html => $self->print('env.html', { json => 1, env => \$env }) }, 1); } END_OF_SUB $COMPILE{login} = __LINE__ . <<'END_OF_SUB'; sub login { # ----------------------------------------------------------------------------- # my ($self, %args) = @_; return $self->print('home.html') unless $self->{cgi}{login}; return $self->print_json_error($self->language('ERR_INVALID_LOGIN')) unless $self->{cgi}{username} and $self->{cgi}{password}; return $self->print_json_error($self->language('ERR_INVALID_USERNAME')) if $self->{cfg}{login}{username} ne $self->{cgi}{username}; return $self->print_json_error($self->language('ERR_INVALID_PASSWORD')) if $self->{cfg}{login}{password} ne GT::FileMan::encrypt($self->{cgi}{password}, $self->{cfg}{login}{password}) and $self->{cfg}{login}{password} ne crypt($self->{cgi}{password}, $self->{cfg}{login}{password}); my $session = $self->session_create($self->{cfg}{login}, $self->{cgi}{cookie}); return $self->print_json_error($self->language('ERR_SESSION')) unless $session; my $user = $self->{cfg}{login}; $user->{permission} = $self->{cfg}{permission}; $self->{session} = { id => $session->{id}, user => $user }; $self->flog("login|$user->{username}"); $self->print_json(undef, 1); } END_OF_SUB $COMPILE{logout} = __LINE__ . <<'END_OF_SUB'; sub logout { # ----------------------------------------------------------------------------- # my ($self, $error) = @_; $self->session_delete(); $self->flog('logout|Logged out'); $self->{session} = undef; my %args = ( json => 1 ); if ($error) { $args{error} = $error } else { $args{message} = $self->language('MSG_LOGOUT'); } $self->print_json({ html => $self->print('login.html', \%args) }, 1, undef, 'LOGGED_OUT'); } END_OF_SUB $COMPILE{help} = __LINE__ . <<'END_OF_SUB'; sub help { # ----------------------------------------------------------------------------- # my $self = shift; my $page = $self->{cgi}{page} || 'help.html'; $page = 'help.html' if $page ne 'help.html' and $page ne 'quicktip.html'; ($page) = $page =~ /^(.*)$/; $self->print($page); } END_OF_SUB $COMPILE{cmdprint} = __LINE__ . <<'END_OF_SUB'; sub cmdprint { # ----------------------------------------------------------------------------- # Print selected file(s) # my $self = shift; my @input = $self->{in}->param('cinput'); # Check the selected files my @files; foreach my $f (@input) { my $file = $self->check_name($f); next unless !$file->{error} and $file->{isfile} and $file->{text}; push @files, $file; } return $self->print_json_error($self->language('ERR_PRINT')) unless scalar @files; return $self->print_json({ files => \@files }, 1) unless $self->{cgi}{print}; my $output = qq~
Gossamer Threads - FileMan
~; if ($self->{cgi}{all}) { # Print multiple files my $flag = 0; foreach my $f (@files) { open (DATA, "< $f->{full_path}") or next; read (DATA, my $data, -s DATA); close DATA; my $style = $flag ? 'style="page-break-before: always;"' : ''; $output .= qq|

$data

|; $flag++; } } else { # Print single file my $file = pop @files; my $next_url = ''; if (scalar @files) { my $hiddens = $self->hiddens(); $next_url = $self->{in}->url(absolute => 1, query_string => 0) . "?cmd=print$hiddens->{hidden_query};print=1"; foreach (@files) { $next_url .= ";cinput=" . $self->{in}->escape($_->{name}); } } open (DATA, "< $file->{full_path}") or return $self->home(error => $self->language('ERR_CANNOT_OPEN', $file->{name}, $!)); read (DATA, my $data, -s DATA); close DATA; $output .= qq|
$data

|; $output = sprintf(qq||, $next_url) . $output if $next_url; } $output .= qq!!; print $self->{in}->header; print $output; } END_OF_SUB sub open_compressed { # ----------------------------------------------------------------------------- # Open a compressed file # my ($self, $file) = @_; my ($ext) = $file->{name} =~ /\.([^.]+)$/; my @files; my $total_size = 0; if (lc $ext eq 'zip') { require Archive::Zip; my $zip = new Archive::Zip($file->{full_path}) or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $file->{name}, $!)); foreach my $f ($zip->members) { my $type = $f->isDirectory ? FOLDER : FILE; my $name = $f->fileName; my $spec = ficon($name, $type); my $perm = $f->unixFileAttributes; my $date = $f->lastModTime; my $size = $f->compressedSize; my @info = ( $type, $spec->{icon}, $name, $size, $spec->{type}, $date, '', $perm, permission($perm), GT::Date::date_get($date, $self->{cfg}{date_format} || '%yyyy%-%mm%-%dd%'), ); $total_size += $size; push @files, \@info; } } else { require GT::Tar; my $tar = GT::Tar->open($file->{full_path}) or return $self->print_json_error($self->language('ERR_CANNOT_OPEN', $file->{name}, $!)); my $files = $tar->files; foreach my $f (@$files) { my $type = $f->{type} == 5 ? FOLDER : FILE; my $spec = ficon($f->{name}, $type); my $owner = (eval {getpwuid($f->{uid})} || '') . ':' . (eval {getgrgid($f->{gid})} || ''); my @info = ( $type, $spec->{icon}, $f->{name}, $f->{size}, $spec->{type}, $f->{mtime}, $owner, $f->{mode}, permission($f->{mode}), GT::Date::date_get($f->{mtime}, $self->{cfg}{date_format} || '%yyyy%-%mm%-%dd%'), ); $total_size += $f->{size}; push @files, \@info; } } return $self->print_json({ html => $self->print('compressed.html', { file => $file, json => 1 }), files => \@files, compressed => 1 }, 1); } sub create_zip { # ----------------------------------------------------------------------------- # my ($self, $files, $to) = @_; require Archive::Zip::Tree; my $zip = Archive::Zip->new(); foreach my $f (@$files) { my $file = $self->check_name($f); next unless !$file->{error} and $file->{read}; # Untaint the path ($file->{full_path}) = $file->{full_path} =~ /^(.*)$/; ($f) = $f =~ /^(.*)$/; if (-d $file->{full_path}) { my @files; find($file->{full_path}, sub { my $fp = shift; my $fn = $fp; $fn =~ s/$self->{cfg}{root_path}\///; if (-d $fp) { $zip->addDirectory($fp, $fn) or warn "$!"; } else { $zip->addFile($fp, $fn) or warn "$!"; } }, { untaint => 1 }); } elsif ($file->{isfile}) { $zip->addFile($file->{full_path}, $f) or warn "$!"; } } my $error = $zip->writeToFileNamed($to) ? $! : ''; return $self->home({ error => { cannot_zip => 1, message => $error }}) if $error; } sub create_tar { # ----------------------------------------------------------------------------- # my ($self, $files, $to) = @_; require GT::Tar; my $fpath = $self->check_path(); my $tar = new GT::Tar($to); my $from = $fpath->{error} ? '' : $fpath->{full_path}; foreach my $f (@$files) { my $file = $self->check_name($f); next unless !$file->{error} and $file->{read}; $tar->add_file($file->{full_path}); } my $items = $tar->files; foreach my $f (@$items) { $f->{name} =~ s,$from/,,; } $tar->write($to); } sub current_path { # ----------------------------------------------------------------------------- # my $self = shift; my $work_path = $self->{cfg}{work_path}; return unless $work_path; my (@paths, $parent); my $work_folders = [split /\//, $work_path]; my $spath = ''; foreach my $f (@$work_folders) { next unless $f; push @paths, { folder => $f, path => $spath }; $spath .= $spath ? "/$f" : $f; $parent = '/'; foreach my $i (0..$#$work_folders - 1) { $parent .= ($parent =~ /\/$/) ? $work_folders->[$i] : "/$work_folders->[$i]"; } } return { parent => $parent, work_path => $work_path, loop => \@paths }; } sub finfo { # ----------------------------------------------------------------------------- # Load file information # my ($self, $full_path, $fname) = @_; my $path = $self->check_path(); my $work_path = $path->{error} ? '' : $path->{full_path}; my ($located) = $full_path =~ /^$work_path\/(.*)/; $located =~ s,/$fname$,,; my (@stat, $type, $link); if (-l $full_path) { @stat = lstat($full_path); $type = -d $full_path ? FOLDER : FILE; $link = readlink $full_path; } else { @stat = stat($full_path); $type = -d $full_path ? FOLDER : FILE; } my $spec = ficon($full_path, $type); my $owner = (eval {getpwuid($stat[4])} || '') . ':' . (eval {getgrgid($stat[5])} || ''); my @info = ( $type, $link ? 'symlink' : $spec->{icon}, $self->{in}->html_escape($fname), $type == FOLDER ? 0 : $stat[7], $link ? $ICONS{symlink}->[1] : $spec->{type}, $stat[9], $owner, sprintf("%04o", ($stat[2] & 07777)), permission($stat[2]), GT::Date::date_get($stat[9], $self->{cfg}{date_format} || '%yyyy%-%mm%-%dd%'), $located eq $fname ? '' : $located, $link || $full_path, ); return \@info; } sub friendly_size { # ----------------------------------------------------------------------------- # Prints out the file size. # my $size = shift; return $size <= 100 ? "$size Bytes" : $size < 10 * KB ? sprintf("%.2f ", $size / KB) . 'KB' : $size < 100 * KB ? sprintf("%.2f ", $size / KB) . 'KB' : $size < MB ? sprintf("%.2f ", $size / KB) . 'KB' : $size < 10 * MB ? sprintf("%.2f ", $size / MB) . 'MB' : $size < 100 * MB ? sprintf("%.2f ", $size / MB) . 'MB' : sprintf("%.2f ", $size / MB) . 'MB'; } sub permission { # ----------------------------------------------------------------------------- # Takes permissions supplied from stat() and prints out in ls -al format. # my $octal = shift; my $string = sprintf "%04o", ($octal & 07777); my @perms = split '', $string; my $result = '--- --- ---'; my @extra_map = ( { mask => 0x4, char => 's' }, { mask => 0x2, char => 's' }, { mask => 0x1, char => 't' }, ); for (my $i = 1; $i < @perms; $i++) { my $j = $i - 1; substr($result, $j * 4 + 0, 1, 'r') if 0x4 & $perms[$i]; substr($result, $j * 4 + 1, 1, 'w') if 0x2 & $perms[$i]; # The display of execute needs to be handled differently as it also shows the # additional permissions my $exec = 0x1 & $perms[$i]; my $char; $char = 'x' if $exec; $char = $extra_map[$j]->{char} if $extra_map[$j]->{mask} & $perms[0]; $char = uc $char if !$exec and $char ne 'x'; substr($result, $j * 4 + 2, 1, $char) if $char; } return $result; } sub ficon { # ----------------------------------------------------------------------------- # Get the associated icon based on a files extension # my ($file, $type) = @_; return { icon => $ICONS{folder}[0], type => $ICONS{folder}[1] } if $type == FOLDER; my ($ext) = $file =~ /\.([^.]+)$/; return { icon => $ICONS{unknown}[0], type => $ICONS{unknown}[1] } unless $ext; foreach (keys %ICONS) { next if $_ =~ /^(?:folder|unknown|parent)$/; return { icon => $ICONS{$_}[0], type => $ICONS{$_}[1]} if $_ =~ /\b\Q$ext\E\b/i; } return { icon => $ICONS{unknown}[0], type => $ICONS{unknown}[1] }; } sub load_htpasswd { # ----------------------------------------------------------------------------- # Load .htpasswd # my $self = shift; my $pwd_path = $self->{default}{pwd_path}; my $htpwd; if ($pwd_path) { my $path = $self->check_path($pwd_path); if (!$path->{error} and $path->{exist}) { my $current = $self->check_path(); $current->{full_path} =~ s/[\/ \:]/\_/g; $htpwd = "$path->{full_path}/.htpass$current->{full_path}"; ($htpwd) = $htpwd =~ /^(.*)$/; # Untaint the path } } unless ($htpwd) { my $fpassword = $self->check_name('.htpasswd'); return if $fpassword->{error}; $htpwd = $fpassword->{full_path}; } my $faccess = $self->check_name(".htaccess"); return unless !$faccess->{error} and $htpwd; my $htacs = $faccess->{full_path}; if (-e $htpwd and $faccess->{exist}) { my ($name_pwd) = $htpwd =~ /^$self->{cfg}{root_path}\/(.*)/; open (HTPWD, "< $htpwd") or return; my @users = map { /^([^:]+)/ ? qq:$1: : qq:'': } ; close HTPWD; return \@users; } } sub create_htaccess { # ----------------------------------------------------------------------------- # Creates the htaccess file. # my ($self, $htaccess, $htpasswd) = @_; my $raq = $ENV{GT_COBALT_RAQ} ? "AuthPAM_Enabled off\n" : ''; open (HTAC, "> $htaccess") or return "open_file: $htaccess - $!"; print HTAC <$to") or return 0; open(SOURCE, "<$from") or return 0; binmode SOURCE; binmode TARGET; my ($buffer, $bit); while (my $rs = read SOURCE, $buffer, $READ_SIZE) { if ($replace) { $buffer = "$bit$buffer" if length $bit; $bit = ($rs == $READ_SIZE and $buffer =~ s/(?:\r|\r?\n)([^\r\n]+)$//) ? $1 : ''; $buffer =~ s/$replace/$with/g; } print TARGET $buffer; } close SOURCE; close TARGET; fchmod($from, $to); return 1; } sub fchmod { # ----------------------------------------------------------------------------- # set chmod # my($from, $to) = @_; $from =~ m,^([/\w.-]+)$,; $from = $1; $to =~ m,^([/\w.-]+)$,; $to = $1; my $stat = [stat($from)]; chmod(@$stat[2], $to); } sub size { # ----------------------------------------------------------------------------- # Load size of files and directories # my ($self, $files) = @_; my $total_size = 0; foreach my $f (@$files) { next unless $f; my $file = $self->check_name($f); next if $f->{error}; if ($file->{isfile}) { $total_size += $file->{size}; } else { find($file->{full_path}, sub { $total_size += -s shift }, { untaint => 1 }); } } return $total_size; } sub check_path { # ----------------------------------------------------------------------------- # Check a directory make sure it safe # It returns a full path or a hash # my ($self, $dir) = @_; my $root = $self->{cfg}{root_path}; my $work = $self->{cfg}{work_path}; $work =~ s/^\/// if $work; my $full_path; unless ($dir) { $full_path = $work ? "$root/$work" : $root; } elsif ($dir eq '/') { $full_path = $root; } else { $full_path = $dir =~ m,^/, ? "$root$dir" : "$root/". ($work ? "$work/" : "") . "$dir"; } # Untaint the path ($full_path) = $full_path =~ /^(.*)$/; if (-e $full_path) { my $current_path = cwd(); chdir($full_path); $full_path = cwd(); # Untaint the path ($current_path) = $current_path =~ /^(.*)$/; chdir($current_path); if ($full_path =~ /^$root(.*)$/) { $work = $1; } else { return { error => $self->language('ERR_OUT_BOUNCE', $dir) }; } } return { error => $self->language('ERR_INVALID_INPUT', $full_path) } if ($self->{cfg}{filename_check} and $full_path !~ m,^([-\w/. ]+)$,); return { work_path => $work, full_path => $full_path, exist => -e $full_path, isdir => -d $full_path, write => -w $full_path, read => -r $full_path }; } sub check_name { # ----------------------------------------------------------------------------- # Check a file/directory name make sure # - Not contain an special characters if the option is on # - Not out side root directory # my ($self, $file) = @_; my ($fname, $fpath); if ($file =~ /^(.*)\/([^\/]+)$/) { $fpath = $1; $fname = $2; } else { $fname = $file; } return { error => $self->language('ERR_INVALID_INPUT') } unless $fname; my $path = $self->check_path($fpath); return { error => $path->{error} } if $path->{error}; return { error => $self->language('ERR_INVALID_INPUT') } if $self->{cfg}{filename_check} and $fname !~ m,^([-\w/. ]+)$,; my $full_path = $path->{full_path} . '/' . $fname; # Untaint the path ($full_path) = $full_path =~ /^(.*)$/; my ($ext) = $fname =~ /\.([^.]+)$/; my $type; if ($ext =~ /^(?:bmp|gif|jpg|tif|tiff|ico|png)$/i) { $type = 'image'; } elsif (-T $full_path and $ext =~ /^(?:html|htm|shtml|shtm)$/i) { $type = 'html'; } elsif (-T $full_path and lc $ext ne 'pdf') { # Open a text file e.g: *.txt, *.html, ect... $type = 'text'; } elsif ($ext =~ /^(?:doc|xls|pdf|mp3|mpga|mpg)$/i) { $type = 'doc'; } elsif ($ext =~ /^(?:tar|gz|zip)$/i) { $type = 'compress'; } else { $type = 'unknown'; } return { name => $file, type => $type, full_path => $full_path, exist => -e $full_path, write => -w $full_path, read => -R $full_path, text => ($ext !~ /^(?:pdf|doc|xls)$/ and -T $full_path), size => -s $full_path, isfile => -f $full_path, }; } sub flog { # ----------------------------------------------------------------------------- # my ($self, $log) = @_; return unless $self->{cfg}{fversion} eq 'multiple'; $self->history($log); } sub upload_progress { # Upload progress # It returns a json object with all progess numbers # my $self = shift; my ($serial) = $self->{cgi}{upload}; # Untaint the path my $logfile = "$self->{cfg}{tmp_path}/$serial"; ($logfile) = $logfile =~ /^(.*)$/; return $self->print_json_error(undef) unless -e $logfile; my ($progress, $currentfile, $totalprogress, $totalsize, $end_time, $start_time, $elapsedtime, $filename, $allowed_space, $free_space) = (); open(READLOGFILE,"< $logfile") or return $self->print_json_error(undef); flock READLOGFILE, 1; seek READLOGFILE, 0, 0; my $line = ; chomp $line; close READLOGFILE or return $self->print_json_error(undef); unlink $logfile; ($progress, $totalsize, $start_time, $end_time, $filename, $allowed_space, $free_space) = split(/:\|:/, $line); $self->print_json({ uploaded => $progress, upload_size => $totalsize, elapsed_time => $end_time - $start_time, allowed_space => $allowed_space, free_space => $free_space, filename => $filename, }, 1); } sub fsystem { my (@args) = @_; my ($output, $error) = ('', ''); my $tmp_output = new GT::TempFile; my $tmp_errors = new GT::TempFile; open(OLDOUT, ">&STDOUT"); open(OLDERR, ">&STDERR"); open(STDOUT, ">$$tmp_output") or return { error => "Can't redirect STDOUT" }; open(STDERR, ">$$tmp_errors") or return { error => "Can't redirect STDERR" }; select(STDERR); $| = 1; select(STDOUT); $| = 1; my $snum = system(@args); # Close and restore STDOUT and STDERR close STDOUT; close STDERR; open(STDOUT, ">&OLDOUT"); open(STDERR, ">&OLDERR"); open (TMP, "< $$tmp_output") or return { error => $! }; read (TMP, $output, -s TMP); close TMP; open (TMP, "< $$tmp_errors") or return { error => $! }; read (TMP, $error, -s TMP); close TMP; unlink $$tmp_output; unlink $$tmp_errors; return $snum ? { error => $output || $error } : { message => $output || $error }; } 1