File Coverage

blib/lib/CGI/FileManager.pm
Criterion Covered Total %
statement 72 293 24.5
branch 5 98 5.1
condition 0 6 0.0
subroutine 19 38 50.0
pod 19 19 100.0
total 115 454 25.3


line stmt bran cond sub pod time code
1             package CGI::FileManager;
2              
3 3     3   112226 use warnings;
  3         7  
  3         1257  
4 3     3   19 use strict;
  3         7  
  3         181  
5              
6             =head1 NAME
7              
8             CGI::FileManager - Managing a directory structure on an HTTP server
9              
10             =head1 SYNOPSIS
11              
12             Enable authenticated users to do full file management on
13             a subdirectory somewhere with a web server installed.
14              
15             After installing the module you have to create a file with usernames and passwords
16             in it. For this we supply cfm-passwd.pl which should have been installed in your PATH.
17             Type:
18              
19             > cfm-passwd.pl /home/user/mypwfile add someuser
20              
21             It will ask for password and the home directory that the use is supposed to be able to
22             manage.
23              
24             Then in nearby CGI script:
25              
26             #!/usr/bin/perl -wT
27             use strict;
28            
29             use CGI::FileManager;
30             my $fm = CGI::FileManager->new(
31             PARAMS => {
32             AUTH => {
33             PASSWD_FILE => "/home/user/mypwfile",
34             }
35             }
36             );
37             $fm->run;
38              
39             Now point your browser to the newly created CGI file and start managing your files.
40              
41              
42             =head1 WARNING
43              
44             This is Software is in Alpha version. Its interface, both human and programatic
45             *will* change. If you are using it, please make sure you always read the Changes
46             section in the documentation.
47              
48              
49             =head1 VERSION
50              
51             Version 0.05
52              
53              
54             =cut
55              
56             our $VERSION = '0.06';
57              
58             =head1 DESCRIPTION
59              
60             Enables one to do basic file management operations on a
61             filesystem under an HTTP server. The actions on the file system
62             provide hooks that let you implement custom behavior on each
63             such event.
64              
65             It can be used as a base class for a simple web application
66             that mainly manipulates files.
67              
68              
69              
70             =head1 Methods
71              
72             =cut
73              
74 3     3   17 use base 'CGI::Application';
  3         186  
  3         5302  
75 3     3   57245 use CGI::Application::Plugin::Session;
  3         58622  
  3         26  
76 3     3   3313 use CGI::Upload;
  3         180385  
  3         231  
77 3     3   35 use File::Spec;
  3         7  
  3         216  
78 3     3   14 use File::Basename qw(dirname);
  3         6  
  3         224  
79 3     3   3712 use Data::Dumper qw(Dumper);
  3         39138  
  3         264  
80 3     3   5326 use HTML::Template;
  3         59503  
  3         142  
81             #use Fcntl qw(:flock);
82             #use POSIX qw(strftime);
83 3     3   3278 use File::Copy qw(move);
  3         9094  
  3         252  
84 3     3   19 use Carp qw(cluck croak);
  3         6  
  3         155  
85              
86 3     3   2890 use CGI::FileManager::Templates;
  3         9  
  3         89  
87 3     3   1336 use CGI::FileManager::Auth;
  3         12  
  3         24113  
88             my $cookiename = "cgi-filemanager";
89              
90              
91             #Standard CGI::Application method
92             #Setup the Session object and the default HTTP headers
93              
94             =head2 cgiapp_init
95              
96             Initialize application (standard CGI::Application)
97              
98             =cut
99             sub cgiapp_init {
100 5     5 1 35773 my $self = shift;
101 5         50 CGI::Session->name($cookiename);
102 5         92 $self->session_config(
103             # CGI_SESSION_OPTIONS => [ "driver:File", $self->query, {Directory => "/tmp"}],
104             COOKIE_PARAMS => {
105             -expires => '+24h',
106             -path => '/',
107             # -domain => $ENV{HTTP_HOST},
108             },
109             SEND_COOKIE => 1,
110             );
111            
112 5 50       255 if ($self->param("TMPL_PATH")) {
113 5         96 $self->tmpl_path([
114             File::Spec->catfile($self->param("TMPL_PATH"), "custom"),
115             File::Spec->catfile($self->param("TMPL_PATH"), "factory"),
116             ]);
117             }
118              
119             $self->header_props(
120 5         352 -expires => '-1d',
121             # I think this this -expires causes some strange behaviour in IE
122             # on the other hand it is needed in Opera to make sure it won't cache pages.
123             -charset => "utf-8",
124             );
125 5         217 $self->session_cookie();
126             }
127              
128              
129              
130             # modes that can be accessed without a valid session
131             my @free_modes = qw(login login_process logout about redirect);
132             my @restricted_modes = qw(
133             list_dir
134             change_dir
135             upload_file
136             delete_file
137             create_directory
138             remove_directory
139             rename_form
140             rename
141             unzip
142             );
143              
144              
145             =head2 setup
146              
147             Standart CGI::Appication method to setup the list of all run modes and the default run mode
148              
149             =cut
150             sub setup {
151 3     3 1 325374 my $self = shift;
152 3         18 $self->start_mode("list_dir");
153 3         47 $self->run_modes(\@free_modes);
154 3         87 $self->run_modes(\@restricted_modes);
155             #$self->run_modes(AUTOLOAD => "autoload");
156             }
157              
158             =head2 cgiapp_prerun
159              
160             Regular CGI::Application method
161              
162             =cut
163             sub cgiapp_prerun {
164 3     3 1 395 my $self = shift;
165 3         21 my $rm = $self->get_current_runmode();
166              
167 3 100       19 return if grep {$rm eq $_} @free_modes;
  15         37  
168              
169             # Redirect to login, if necessary
170 1 50       5 if (not $self->session->param('loggedin') ) {
171 1         28 $self->header_type("redirect");
172 1         22 $self->header_props(-url => "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}?rm=login");
173 1         37 $self->prerun_mode("redirect");
174 1         11 return;
175             }
176             }
177              
178              
179             sub _untaint_path {
180 0     0   0 my ($self, $path) = @_;
181              
182 0 0       0 return "" if not defined $path;
183 0 0       0 return "" if $path =~ /\.\./;
184 0 0       0 if ($path =~ m{^([\w./-]+)$}) {
185 0         0 return $1;
186             }
187              
188 0         0 return "";
189             }
190              
191              
192             sub _untaint {
193 0     0   0 my ($self, $filename) = @_;
194              
195 0 0       0 return if not defined $filename;
196              
197 0 0       0 return if $filename =~ /\.\./;
198 0 0       0 if ($filename =~ /^([\w.-]+)$/) {
199 0         0 return $1;
200             }
201 0         0 return;
202             }
203              
204              
205             =head2 redirect
206              
207             Just to easily redirect to the home page
208              
209             =cut
210             sub redirect {
211 1     1 1 66 my $self = shift;
212 1         3 return;
213             # my $target = shift;
214             # $self->header_type("redirect");
215             # $self->header_props(-url => "http://$ENV{HTTP_HOST}/$target");
216             }
217            
218              
219              
220             =head2 load_tmpl
221              
222             Change the default behaviour of CGI::Application by overriding this
223             method. By default we'll load the template from within our module.
224              
225             =cut
226             sub load_tmpl {
227 2     2 1 6 my $self = shift;
228              
229 2         3 my $t;
230 2 50       11 if ($self->param("TMPL_PATH")) {
231 2         55 $t = $self->SUPER::load_tmpl(@_);
232             } else {
233 0         0 my $name = shift;
234            
235 0         0 my $template = CGI::FileManager::Templates::_get_template($name);
236 0 0       0 croak "Could not load template '$name'" if not $template;
237              
238 0         0 $t = HTML::Template->new_scalar_ref(\$template, @_);
239             }
240            
241              
242             # my $t = $self->SUPER::load_tmpl(@_,
243             # die_on_bad_params => -e ($self->param("ROOT") . "/die_on_bad_param") ? 1 : 0
244             # );
245 2         4579 return $t;
246             }
247              
248             =head2 message
249              
250             Print an arbitrary message to the next page
251              
252             =cut
253             sub message {
254 0     0 1 0 my $self = shift;
255 0         0 my $message = shift;
256            
257 0         0 my $t = $self->load_tmpl(
258             "message",
259             );
260              
261 0 0       0 $t->param("message" => $message) if $message;
262 0         0 return $t->output;
263             }
264              
265              
266             =head2 login
267              
268             Show login form
269              
270             =cut
271             sub login {
272 2     2 1 129 my $self = shift;
273 2         4 my $errs = shift;
274 2         8 my $q = $self->query;
275            
276 2         21 my $t = $self->load_tmpl(
277             "login",
278             associate => $q,
279             );
280              
281 2         11 $t->param($_ => 1) foreach @$errs;
282 2         11 return $t->output;
283             }
284              
285              
286             =head2 login_process
287              
288             Processing the login information, checking authentication, configuring the session object
289             or giving error message.
290              
291             =cut
292             sub login_process {
293 0     0 1   my $self = shift;
294 0           my $q = $self->query;
295              
296 0 0 0       if (not $q->param("username") or not $q->param("password")) {
297 0           return $self->login(["login_failed"]);
298             }
299              
300 0           my $auth = $self->authenticate();
301 0 0         if ($auth->verify($q->param("username"), $q->param("password"))) {
302 0           $self->session->param(loggedin => 1);
303 0           $self->session->param(username => $q->param("username"));
304 0           $self->session->param(homedir => $auth->home($q->param("username")));
305             # $self->session->param(workdir => $auth->home($q->param("username")));
306 0           $self->header_type("redirect");
307 0           $self->header_props(-url => "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}");
308 0           return;
309             } else {
310 0           return $self->login(["login_failed"]);
311             }
312             }
313              
314             =head2 authenticate
315              
316             Called without parameter.
317             Returns an objects that is capable to authenticate a user.
318              
319             By default it returns a CGI::FileManager::Auth object.
320              
321             It is planned that this method will be overriden by the user to be able to replace the
322             authentication back-end. Currently the requirements from the returned object is to have
323             these methods:
324              
325             $a->verify(username, password) returns true/false
326             $a->home(username) return the full path to the home directory of the given user
327              
328             WARNING:
329             this interface might change in the future, before we reach version 1.00 Check the Changes.
330              
331             =cut
332             sub authenticate {
333 0     0 1   my $self = shift;
334 0           return CGI::FileManager::Auth->new($self->param("AUTH"));
335             }
336              
337              
338             =head2 logout
339              
340             logout and mark the session accordingly.
341              
342             =cut
343             sub logout {
344 0     0 1   my $self = shift;
345 0           $self->session->param(loggedin => 0);
346 0           my $t = $self->load_tmpl(
347             "logout",
348             );
349 0           $t->output;
350             }
351              
352              
353              
354             =head2 change_dir
355              
356             Changes the current directory and then lists the new current directory
357              
358             =cut
359             sub change_dir {
360 0     0 1   my $self = shift;
361 0           my $q = $self->query;
362              
363 0           my $workdir = $self->_untaint_path($q->param("workdir"));
364 0           my $homedir = $self->session->param("homedir");
365              
366 0           my $dir = $q->param("dir");
367 0 0         if (not defined $dir) {
368 0           warn "change_dir called without a directory name\n";
369 0           return $self->list_dir;
370             }
371            
372             # check santity of the directory
373             # something else, does this directory exist ?
374 0 0         if ($dir eq "..") {
375             # ".." are we at the root ?
376 0 0         if ($workdir eq "") {
377             # do nothing (maybe a beep ?)
378 0           return $self->list_dir;
379             } else {
380             # shorten the path by one
381 0           $workdir = dirname $workdir;
382 0           $self->header_type("redirect");
383 0           $self->header_props(-url => "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}?rm=list_dir;workdir=$workdir");
384 0           return $self->redirect;
385             #Redirect
386 0           return $self->list_dir;
387             }
388             } else {
389 0 0         if ($dir =~ /\.\./) {
390 0           warn "change_dir: Two dots ? '$dir'";
391 0           return $self->message("Hmm, two dots in a regular file ? Please contact the administrator");
392             }
393 0 0         if ($dir =~ /^([\w.-]+)$/) {
394 0           $dir = $1;
395 0           $workdir = File::Spec->catfile($workdir, $dir);
396 0           my $path = File::Spec->catfile($homedir, $workdir);
397 0 0         if (-d $path) {
398 0           $self->header_type("redirect");
399 0           $self->header_props(-url => "http://$ENV{HTTP_HOST}$ENV{SCRIPT_NAME}?rm=list_dir;workdir=$workdir");
400 0           return $self->redirect;
401             #$self->session->param(workdir => $workdir);
402             #return $self->list_dir;
403             } else {
404             # after changing directory people might press back ...
405             # and then the whole thing can get scread up not only the change directory
406             # but if they now delete a file that happen to exist both in the current directory
407             # and in its parent (which is currenly shown in the browser) the file will be deleted
408             # from the "current directory", I think the only solution is that the user supplies us
409             # with full (virtual) path name for every action.
410             # This seems to be easy regarding action on existing files as they are all done by clicking
411             # on links and the links can contain.
412             # Regardin upload/create dir and later create file we have to know where should the thing go
413             # - what does the user think is the current working directory. For such operations we can
414             # hide the workdir in a hidden field in the form.
415             #
416             # In either case we have to make sure the full virtual directory is something the user
417             # has right to access.
418            
419             #my $workdir_name = basename $workdir;
420             #if ($workdir_name eq $dir) {
421             # return $self->message("Heuristics !");
422             #} else {
423 0           warn "change_dir: Trying to change to invalid directory ? '$workdir'$dir'";
424 0           return $self->message("It does not seem to be a correct directory. Please contact the administrator");
425             #}
426             }
427             } else {
428 0           warn "change_dir: Bad regex, or bad visitor ? '$dir'";
429 0           return $self->message("Hmm, we don't recognize this. Please contact the administrator");
430             }
431             }
432            
433 0           warn "should never got here....";
434 0           return $self->list_dir;
435             }
436              
437             =head2 list_dir
438              
439             Listing the content of a directory
440              
441             =cut
442             sub list_dir {
443 0     0 1   my $self = shift;
444 0           my $msgs = shift;
445              
446 0           my $q = $self->query;
447              
448 0           my $workdir = $self->_untaint_path($q->param("workdir"));
449 0           my $homedir = $self->session->param("homedir");
450 0           my $path = File::Spec->catfile($homedir, $workdir);
451              
452              
453 0           my $t = $self->load_tmpl(
454             "list_dir",
455             associate => $q,
456             loop_context_vars => 1,
457             );
458 0 0         if (opendir my $dh, $path) {
459 0 0         my @entries = grep {$_ ne "." and $_ ne ".."} readdir $dh;
  0            
460 0 0 0       if ($workdir ne "" and $workdir ne "/") {
461 0           unshift @entries, "..";
462             }
463 0           my @files;
464            
465 0           foreach my $f (@entries) {
466 0           my $full = File::Spec->catfile($path, $f);
467 0 0         push @files, {
    0          
    0          
468             filename => $f,
469             filetype => $self->_file_type($full),
470             subdir => -d $full,
471             zipfile => ($full =~ /\.zip/i ? 1 : 0),
472             filedate => scalar (localtime((stat($full))[9])),
473             size => (stat($full))[7],
474             delete_link => $f eq ".." ? "" : $self->_delete_link($full),
475             rename_link => $f eq ".." ? "" : $self->_rename_link($full),
476             workdir => $workdir,
477             };
478             }
479            
480 0           $t->param(workdir => $workdir);
481 0           $t->param(files => \@files);
482 0           $t->param(version => $VERSION);
483             }
484 0           $t->param($_ => 1) foreach @$msgs;
485              
486 0           return $t->output;
487             }
488              
489             # returns the type of the given file
490             sub _file_type {
491 0     0     my ($self, $file) = @_;
492 0 0         return "dir" if -d $file;
493 0 0         return "file" if -f $file;
494 0           return "n/a";
495             }
496              
497             sub _delete_link {
498 0     0     my ($self, $file) = @_;
499 0 0         return "rm=remove_directory;dir=" if -d $file;
500 0 0         return "rm=delete_file;filename=" if -f $file;
501 0           return "";
502             }
503              
504             sub _rename_link {
505 0     0     my ($self, $file) = @_;
506 0 0         return "rm=rename_form;filename=" if -d $file;
507 0 0         return "rm=rename_form;filename=" if -f $file;
508 0           return "";
509             }
510              
511              
512             =head2 delete_file
513              
514             Delete a file from the server
515              
516             =cut
517             sub delete_file {
518 0     0 1   my ($self) = @_;
519 0           my $q = $self->query;
520              
521 0           my $filename = $q->param("filename");
522 0           $filename = $self->_untaint($filename);
523              
524 0 0         if (not $filename) {
525 0           warn "Tainted filename: '" . $q->param("filename") . "'";
526 0           return $self->message("Invalid filename. Please contact the system administrator");
527             }
528 0           my $homedir = $self->session->param("homedir");
529 0           my $workdir = $self->_untaint_path($q->param("workdir"));
530            
531 0           $filename = File::Spec->catfile($homedir, $workdir, $filename);
532              
533 0           unlink $filename;
534              
535 0           $self->list_dir;
536             }
537              
538             =head2 remove_directory
539              
540             Remove a directory
541              
542             =cut
543             sub remove_directory {
544 0     0 1   my ($self) = @_;
545 0           my $q = $self->query;
546              
547 0           my $dir = $q->param("dir");
548 0           $dir = $self->_untaint($dir);
549              
550 0 0         if (not $dir) {
551 0           warn "Tainted diretory name: '" . $q->param("dir") . "'";
552 0           return $self->message("Invalid directory name. Please contact the system administrator");
553             }
554 0           my $homedir = $self->session->param("homedir");
555 0           my $workdir = $self->_untaint_path($q->param("workdir"));
556            
557 0           $dir = File::Spec->catfile($homedir, $workdir, $dir);
558              
559 0           rmdir $dir;
560              
561 0           $self->list_dir;
562             }
563              
564             =head2 unzip
565              
566             unzip
567              
568             =cut
569             sub unzip {
570 0     0 1   my $self = shift;
571 0           my $q = $self->query;
572              
573 0           my $filename = $q->param("filename");
574 0           $filename = $self->_untaint($filename);
575 0 0         $filename = "" if $filename !~ /\.zip/i;
576              
577 0 0         if (not $filename) {
578 0           warn "Tainted or not zip file name: '" . $q->param("filename") . "'";
579 0           return $self->message("Invalid filename '" . $q->param("filename") . "'. Please contact the system administrator");
580             }
581              
582 0           my $homedir = $self->session->param("homedir");
583 0           my $workdir = $self->_untaint_path($q->param("workdir"));
584              
585 0           $filename = File::Spec->catfile($homedir, $workdir, $filename);
586 0 0         if (not -e $filename) {
587 0           warn "Could not find '$filename' for unzip";
588 0           return $self->message("File does not seem to exist.");
589             }
590              
591 0           my $dir = File::Spec->catfile($homedir, $workdir);
592 0           warn "Unzipping $filename in $dir";
593 0           warn `cd $dir; /usr/bin/unzip -o $filename`;
594              
595 0           $self->list_dir;
596             }
597            
598              
599             =head2 rename_form
600              
601             Rename file form
602              
603             =cut
604             sub rename_form {
605 0     0 1   my $self = shift;
606 0           my $q = $self->query;
607            
608 0           my $t = $self->load_tmpl(
609             "rename_form",
610             associate => $q,
611             );
612 0           return $t->output;
613             }
614              
615              
616             sub _move {
617 0     0     my ($self, $old, $new) = @_;
618            
619 0 0         if (-e $new) {
620 0           return $self->message("Target file already exist");
621             }
622 0           move $old, $new;
623 0           return $self->list_dir;
624             }
625              
626             =head2 rename
627              
628             Rename file
629              
630             =cut
631             sub rename {
632 0     0 1   my $self = shift;
633 0           my $q = $self->query;
634              
635 0           my $old = $q->param("filename");
636 0           my $old_name = $old = $self->_untaint($old);
637              
638 0 0         if (not $old) {
639 0           warn "Tainted file name: '" . $q->param("filename") . "'";
640 0           return $self->message("Invalid filename '" . $q->param("filename") . "'. Please contact the system administrator");
641             }
642              
643 0           my $homedir = $self->session->param("homedir");
644 0           my $workdir = $self->_untaint_path($q->param("workdir"));
645              
646 0           $old = File::Spec->catfile($homedir, $workdir, $old);
647 0 0         if (not -e $old) {
648 0           warn "Could not find '$old' for rename";
649 0           return $self->message("File does not seem to exist.");
650             }
651              
652              
653 0           my $new = $q->param("newname");
654 0           my $targetdir;
655 0 0         if ($new eq "..") {
656 0 0         if ($workdir eq "") {
657 0           warn "Trying to move something above the root: '" . $q->param("filename") . "'";
658 0           return $self->message("This wont work. Please contact the system administrator");
659             } else {
660 0           $new = File::Spec->catfile($homedir, dirname($workdir), $old_name);
661 0           return $self->_move($old, $new);
662             }
663             }
664              
665 0           $new = $self->_untaint($new);
666              
667 0 0         if (not $new) {
668 0           warn "Tainted file name: '" . $q->param("newname") . "'";
669 0           return $self->message("Invalid filename. '" . $q->param("newname") . "' Please contact the system administrator");
670             }
671              
672 0           $new = File::Spec->catfile($homedir, $workdir, $new);
673 0 0         if (-d $new) {
674 0           $new = File::Spec->catfile($new, $old_name);
675             }
676 0           return $self->_move($old, $new);
677             }
678              
679              
680             =head2 upload_file
681              
682             Upload a file
683              
684             =cut
685             sub upload_file {
686 0     0 1   my $self = shift;
687 0           my $q = $self->query;
688              
689 0           my $homedir = $self->session->param("homedir");
690 0           my $workdir = $self->_untaint_path($q->param("workdir"));
691              
692 0           my $upload = CGI::Upload->new();
693 0           my $file_name = $upload->file_name('filename');
694 0           my $in = $upload->file_handle('filename');
695            
696 0 0         if (ref $in ne "IO::File") {
697 0           warn "No file handle in upload ? '$file_name'";
698 0           return $self->message("Hmm, strange. Please contact the administrator");
699             }
700              
701 0 0         if ($file_name =~ /\.\./) {
702 0           warn "two dots in upload file ? '$file_name'";
703 0           return $self->message("Hmm, we don't recognize this. Please contact the administrator");
704             }
705 0 0         if ($file_name =~ /^([\w.-]+)$/) {
706 0           $file_name = $1;
707 0 0         if (open my $out, ">", File::Spec->catfile($homedir, $workdir,$file_name)) {
708 0           my $buff;
709 0           while (read $in, $buff, 500) {
710 0           print $out $buff;
711             }
712             } else {
713 0           warn "Could not open local file: '$file_name'";
714 0           return $self->message("Could not open local file. Please contact the administrator");
715             }
716             } else {
717 0           warn "Invalid name for upload file ? '$file_name'";
718 0           return $self->message("Hmm, we don't recognize this. Please contact the administrator");
719             }
720              
721 0           $self->list_dir;
722             }
723              
724             =head2 create_directory
725              
726             Create a directory
727              
728             =cut
729             sub create_directory {
730 0     0 1   my $self = shift;
731 0           my $q = $self->query;
732              
733 0           my $homedir = $self->session->param("homedir");
734 0           my $workdir = $self->_untaint_path($q->param("workdir"));
735 0           my $dir = $q->param("dir");
736 0           $dir = $self->_untaint($dir);
737 0 0         if (not $dir) {
738 0           warn "invalid directory: '" . $q->param("dir") . "'";
739 0           return $self->message("Invalid directory name ? Contact the administrator");
740             }
741              
742 0           mkdir File::Spec->catfile($homedir, $workdir, $dir);
743              
744 0           $self->list_dir;
745             }
746              
747             =head2 DEFAULT
748              
749             To get the default behavior you can write the following code.
750             The module will use the built in templates to create the pages.
751              
752             #!/usr/bin/perl -wT
753             use strict;
754            
755             use CGI::FileManager;
756             my $fm = CGI::FileManager->new(
757             PARAMS => {
758             AUTH => {
759             PASSWD_FILE => "/home/user/mypwfile",
760             }
761             }
762             );
763             $fm->run;
764              
765              
766             =over 4
767              
768             =item new(OPTIONS)
769              
770             =back
771              
772             =head2 META-DATA
773              
774             Theoretically we could manage some meta-data about each file in some database that
775             can be either outside our virtual file system or can be a special file in each
776             directory.
777              
778              
779             =cut
780              
781             # Hmm, either this module does not deal at all with authentication and assumes that
782             # something around it can deal with this.
783              
784             # But we also would like to be able to create a list of users and for each user to assign
785             # a virtual directory. Onto this virtual directory we would like to be able to "mount"
786             # any subdirectory of the real file system. We can even go further and provide options
787             # to this "mount" such as read-only (for that specific user) or read/write.
788             #=head2 Quota
789             #Maybe we can also implement some quota on the file system ?
790              
791              
792             =head2 Limitations
793              
794             The user running the web server has to have read/write access on the relevant part
795             of the file system in order to carry out all the functions.
796              
797             =head1 USE CASES
798              
799             =head2 Virtual web hosting with no ftp access for one user
800              
801             A single user needs authentication and full access to one directory tree.
802             This does not work yet.
803            
804             #!/usr/bin/perl -T
805            
806             use CGI::FileManager;
807             my $fm = CGI::FileManager->new({
808             ROOT => "/home/gabor/web/client1",
809             AUTH => ["george", "WE#$%^DFRE"], # the latter is the crypt-ed password we expect
810             });
811             $fm->run;
812              
813             =head2 Virtual web hosting with no ftp access for a number of users
814              
815             A number of users need authentication and full access to one directory tree per user.
816              
817             #!/usr/bin/perl -T
818            
819             use CGI::FileManager;
820             my $fm = CGI::FileManager->new(
821             PARAMS => {
822             AUTH => {
823             PASSWD_FILE => "/home/user/mypwfile",
824             }
825             }
826             );
827             $fm->run;
828              
829             The mypwfile file looks similar to an /etc/passwd file:
830             username:password:uid:gid:geco:homedir:shell
831              
832             gid and shell are currently not used
833             homedir is the directory the user has rights for
834             password is encrypted by crypt
835             uid is just a unique number
836              
837             =head1 Changes
838              
839              
840             =head2 v0.01 2004 June 27
841              
842             Initial release
843              
844             =head2 v0.02_01
845              
846             Move file/directory
847             Unzip file (.zip)
848              
849             =head2 v0.02_02
850              
851             Separate CGI::FileManager::Templates
852             add cfm-install.pl install script
853              
854              
855             Use CGI::Application::Plugin::Session
856             remove catching the warning of CA and require higher version of CA
857             add a test that test a particular warning
858             some subs were called as functions, now they are called as methods allowing better subclassing
859              
860             =head1 TODO
861              
862             - install the module as regular CPAN module and add a script that will generate the templates
863             and hard-code their location in the script.
864            
865             - Replace the Unix::ConfigFile with my own implementation
866              
867             Test the module on Windows and find out what need to be done to pass the windows
868             tests ? Especially look at Unix::ConfigFile
869              
870             Show most of the error messages on the directory listing page
871            
872             Support for filenames with funny characters (eg. space)
873              
874             Test all the functions, look for security issues !
875             Show the current directory (the virtual path)
876             Separate footer/header
877             Enable external templates
878              
879             Security issues: can I be sure that unzipping a file will open files only under the current directory ?
880             What should I do in case a file that comes from an unzip operation already exists ?
881              
882             ZIP: currently the path to unzip is hard coded. It probably should be replaced by Archive::Zip
883              
884             More fancy things:
885             Create file
886             Copy file/directory
887             Unzip file (tar/gz/zip)
888             Edit file (simple editor)
889              
890             look at CGI::Explorer and check what is the relationsip to it ?
891              
892             =head1 Author
893              
894             Gabor Szabo, C<< >>
895              
896             =head1 Bugs
897              
898             Please report any bugs or feature requests to
899             C, or through the web interface at
900             L. I will be notified, and then you'll automatically
901             be notified of progress on your bug as I make changes.
902              
903              
904             =head1 Copyright & License
905              
906             Copyright 2004 Gabor Szabo, All Rights Reserved.
907             L
908              
909             This program is free software; you can redistribute it and/or modify it
910             under the same terms as Perl itself.
911              
912             =head1 See also
913              
914             CGI::Upload, WWW::FileManager, CGI::Uploader
915              
916             =cut
917              
918             1;
919