]> git.p6c8.net - devedit.git/blob - modules/Command.pm
e2a00636a9b3647a907b5d1ddca613efd2d950c5
[devedit.git] / modules / Command.pm
1 package Command;
2
3 #
4 # Dev-Editor - Module Command
5 #
6 # Execute Dev-Editor's commands
7 #
8 # Author: Patrick Canterino <patshaping@gmx.net>
9 # Last modified: 2004-03-15
10 #
11
12 use strict;
13
14 use vars qw(@EXPORT);
15
16 use File::Access;
17 use File::Copy;
18 use File::Path;
19
20 use POSIX qw(strftime);
21 use Tool;
22
23 use CGI qw(header);
24 use HTML::Entities;
25 use Output;
26 use Template;
27
28 my $script = $ENV{'SCRIPT_NAME'};
29
30 my %dispatch = ('show' => \&exec_show,
31 'beginedit' => \&exec_beginedit,
32 'canceledit' => \&exec_canceledit,
33 'endedit' => \&exec_endedit,
34 'mkdir' => \&exec_mkdir,
35 'mkfile' => \&exec_mkfile,
36 'upload' => \&exec_upload,
37 'copy' => \&exec_copy,
38 'rename' => \&exec_rename,
39 'remove' => \&exec_remove,
40 'unlock' => \&exec_unlock,
41 'about' => \&exec_about
42 );
43
44 ### Export ###
45
46 use base qw(Exporter);
47
48 @EXPORT = qw(exec_command);
49
50 # exec_command()
51 #
52 # Execute the specified command
53 #
54 # Params: 1. Command to execute
55 # 2. Reference to user input hash
56 # 3. Reference to config hash
57 #
58 # Return: Output of the command (Scalar Reference)
59
60 sub exec_command($$$)
61 {
62 my ($command,$data,$config) = @_;
63
64 return error($config->{'err_cmd_unknown'},'/',{COMMAND => $command}) unless($dispatch{$command});
65
66 my $output = &{$dispatch{$command}}($data,$config);
67 return $output;
68 }
69
70 # exec_show()
71 #
72 # View a directory or a file
73 #
74 # Params: 1. Reference to user input hash
75 # 2. Reference to config hash
76 #
77 # Return: Output of the command (Scalar Reference)
78
79 sub exec_show($$)
80 {
81 my ($data,$config) = @_;
82 my $physical = $data->{'physical'};
83 my $virtual = $data->{'virtual'};
84
85 my $tpl = new Template;
86
87 if(-d $physical)
88 {
89 # Create directory listing
90
91 my $direntries = dir_read($physical);
92 return error($config->{'dir_read_failed'},upper_path($virtual),{DIR => '$virtual'}) unless($direntries);
93
94 my $files = $direntries->{'files'};
95 my $dirs = $direntries->{'dirs'};
96
97 my $dirlist = "";
98
99 # Create the link to the upper directory
100 # (only if we are not in the root directory)
101
102 unless($virtual eq "/")
103 {
104 my @stat = stat($physical."/..");
105
106 my $udtpl = new Template;
107 $udtpl->read_file($config->{'tpl_dirlist_up'});
108
109 $udtpl->fillin("UPPER_DIR",encode_entities(upper_path($virtual)));
110 $udtpl->fillin("DATE",strftime($config->{'timeformat'},localtime($stat[9])));
111
112 $dirlist .= $udtpl->get_template;
113 }
114
115 # Directories
116
117 foreach my $dir(@$dirs)
118 {
119 my @stat = stat($physical."/".$dir);
120 my $virt_path = encode_entities($virtual.$dir."/");
121
122 my $dtpl = new Template;
123 $dtpl->read_file($config->{'tpl_dirlist_dir'});
124
125 $dtpl->fillin("DIR",$virt_path);
126 $dtpl->fillin("DIR_NAME",$dir);
127 $dtpl->fillin("DATE",strftime($config->{'timeformat'},localtime($stat[9])));
128 $dtpl->fillin("URL",equal_url($config->{'httproot'},$virt_path));
129
130 $dirlist .= $dtpl->get_template;
131 }
132
133 # Files
134
135 foreach my $file(@$files)
136 {
137 my $phys_path = $physical."/".$file;
138 my $virt_path = encode_entities($virtual.$file);
139
140 my @stat = stat($phys_path);
141 my $in_use = $data->{'uselist'}->in_use($virtual.$file);
142
143 my $ftpl = new Template;
144 $ftpl->read_file($config->{'tpl_dirlist_file'});
145
146 $ftpl->fillin("FILE",$virt_path);
147 $ftpl->fillin("FILE_NAME",$file);
148 $ftpl->fillin("SIZE",$stat[7]);
149 $ftpl->fillin("DATE",strftime($config->{'timeformat'},localtime($stat[9])));
150 $ftpl->fillin("URL",equal_url($config->{'httproot'},$virt_path));
151
152 $ftpl->parse_if_block("not_readable",not -r $phys_path);
153 $ftpl->parse_if_block("binary",-B $phys_path);
154 $ftpl->parse_if_block("readonly",not -w $phys_path);
155
156 $ftpl->parse_if_block("viewable",-r $phys_path && -T $phys_path);
157 $ftpl->parse_if_block("editable",-w $phys_path && -r $phys_path && -T $phys_path && not $in_use);
158
159 $ftpl->parse_if_block("in_use",$in_use);
160 $ftpl->parse_if_block("unused",not $in_use);
161
162 $dirlist .= $ftpl->get_template;
163 }
164
165 $tpl->read_file($config->{'tpl_dirlist'});
166
167 $tpl->fillin("DIRLIST",$dirlist);
168 $tpl->fillin("DIR",$virtual);
169 $tpl->fillin("SCRIPT",$script);
170 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
171 }
172 else
173 {
174 # View a file
175
176 return error($config->{'err_noview'},upper_path($virtual)) unless(-r $physical);
177
178 # Check on binary files
179 # We have to do it in this way, or empty files
180 # will be recognized as binary files
181
182 unless(-T $physical)
183 {
184 # Binary file
185
186 return error($config->{'err_binary'},upper_path($virtual));
187 }
188 else
189 {
190 # Text file
191
192 my $content = file_read($physical);
193 $$content =~ s/\015\012|\012|\015/\n/g;
194
195 $tpl->read_file($config->{'tpl_viewfile'});
196
197 $tpl->fillin("FILE",$virtual);
198 $tpl->fillin("DIR",upper_path($virtual));
199 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
200 $tpl->fillin("SCRIPT",$script);
201 $tpl->fillin("CONTENT",encode_entities($$content));
202 }
203 }
204
205 my $output = header(-type => "text/html");
206 $output .= $tpl->get_template;
207
208 return \$output;
209 }
210
211 # exec_beginedit
212 #
213 # Lock a file and display a form to edit it
214 #
215 # Params: 1. Reference to user input hash
216 # 2. Reference to config hash
217 #
218 # Return: Output of the command (Scalar Reference)
219
220 sub exec_beginedit($$)
221 {
222 my ($data,$config) = @_;
223 my $physical = $data->{'physical'};
224 my $virtual = $data->{'virtual'};
225 my $uselist = $data->{'uselist'};
226
227 return error($config->{'err_editdir'},upper_path($virtual)) if(-d $physical);
228 return error($config->{'err_in_use'},upper_path($virtual),{FILE => $virtual}) if($uselist->in_use($virtual));
229 return error($config->{'err_noedit'},upper_path($virtual)) unless(-r $physical && -w $physical);
230
231 # Check on binary files
232
233 unless(-T $physical)
234 {
235 # Binary file
236
237 return error($config->{'err_binary'},upper_path($virtual));
238 }
239 else
240 {
241 # Text file
242
243 $uselist->add_file($virtual);
244 $uselist->save;
245
246 my $content = file_read($physical);
247 $$content =~ s/\015\012|\012|\015/\n/g;
248
249 my $tpl = new Template;
250 $tpl->read_file($config->{'tpl_editfile'});
251
252 $tpl->fillin("FILE",$virtual);
253 $tpl->fillin("DIR",upper_path($virtual));
254 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
255 $tpl->fillin("SCRIPT",$script);
256 $tpl->fillin("CONTENT",encode_entities($$content));
257
258 my $output = header(-type => "text/html");
259 $output .= $tpl->get_template;
260
261 return \$output;
262 }
263 }
264
265 # exec_canceledit()
266 #
267 # Abort file editing
268 #
269 # Params: 1. Reference to user input hash
270 # 2. Reference to config hash
271 #
272 # Return: Output of the command (Scalar Reference)
273
274 sub exec_canceledit($$)
275 {
276 my ($data,$config) = @_;
277 my $virtual = $data->{'virtual'};
278
279 file_unlock($data->{'uselist'},$virtual);
280 return devedit_reload({command => 'show', file => upper_path($virtual)});
281 }
282
283 # exec_endedit()
284 #
285 # Save a file, unlock it and return to directory view
286 #
287 # Params: 1. Reference to user input hash
288 # 2. Reference to config hash
289 #
290 # Return: Output of the command (Scalar Reference)
291
292 sub exec_endedit($$)
293 {
294 my ($data,$config) = @_;
295 my $physical = $data->{'physical'};
296 my $virtual = $data->{'virtual'};
297 my $content = $data->{'cgi'}->param('filecontent');
298 my $uselist = $data->{'uselist'};
299
300 # Normalize newlines
301
302 $content =~ s/\015\012|\012|\015/\n/g;
303
304 if($data->{'cgi'}->param('encode_iso'))
305 {
306 # Encode all ISO-8859-1 special chars
307
308 $content = encode_entities($content,"\200-\377");
309 }
310
311 if($data->{'cgi'}->param('saveas'))
312 {
313 # Create the new filename
314
315 $physical = $data->{'new_physical'};
316 $virtual = $data->{'new_virtual'};
317
318 # Check if someone else is editing the new file
319
320 return error($config->{'err_in_use'},upper_path($virtual),{FILE => $virtual}) if($uselist->in_use($virtual));
321 }
322
323 return error($config->{'err_editdir'},upper_path($virtual)) if(-d $physical);
324 return error($config->{'err_noedit'}, upper_path($virtual)) unless(-r $physical && -w $physical);
325
326 if(file_save($physical,\$content))
327 {
328 # Saving of the file was successful - so unlock it!
329
330 file_unlock($uselist,$data->{'virtual'});
331 # ^^^^^^^^^^^^^^^^^^
332 # Maybe the user saved the file using another filename...
333 # But we have to unlock the original file!
334
335 return devedit_reload({command => 'show', file => upper_path($virtual)});
336 }
337 else
338 {
339 return error($config->{'err_edit_failed'},upper_path($virtual),{FILE => $virtual});
340 }
341 }
342
343 # exec_mkfile()
344 #
345 # Create a file and return to directory view
346 #
347 # Params: 1. Reference to user input hash
348 # 2. Reference to config hash
349 #
350 # Return: Output of the command (Scalar Reference)
351
352 sub exec_mkfile($$)
353 {
354 my ($data,$config) = @_;
355 my $new_physical = $data->{'new_physical'};
356 my $new_virtual = $data->{'new_virtual'};
357 my $dir = upper_path($new_virtual);
358 $new_virtual = encode_entities($new_virtual);
359
360 return error($config->{'err_file_exists'},$dir,{FILE => $new_virtual}) if(-e $new_physical);
361
362 file_create($new_physical) or return error($config->{'err_mkfile_failed'},$dir,{FILE => $new_virtual});
363 return devedit_reload({command => 'show', file => $dir});
364 }
365
366 # exec_mkdir()
367 #
368 # Create a directory and return to directory view
369 #
370 # Params: 1. Reference to user input hash
371 # 2. Reference to config hash
372 #
373 # Return: Output of the command (Scalar Reference)
374
375 sub exec_mkdir($$)
376 {
377 my ($data,$config) = @_;
378 my $new_physical = $data->{'new_physical'};
379 my $new_virtual = $data->{'new_virtual'};
380 my $dir = upper_path($new_virtual);
381 $new_virtual = encode_entities($new_virtual);
382
383 return error($config->{'err_file_exists'},$dir,{FILE => $new_virtual}) if(-e $new_physical);
384
385 mkdir($new_physical,0777) or return error($config->{'err_mkdir_failed'},$dir,{DIR => $new_virtual});
386 return devedit_reload({command => 'show', file => $dir});
387 }
388
389 # exec_upload()
390 #
391 # Upload a file
392 #
393 # Params: 1. Reference to user input hash
394 # 2. Reference to config hash
395 #
396 # Return: Output of the command (Scalar Reference)
397
398 sub exec_upload($$)
399 {
400 my ($data,$config) = @_;
401 my $physical = $data->{'physical'};
402 my $virtual = $data->{'virtual'};
403 my $cgi = $data->{'cgi'};
404
405 if(my $uploaded_file = $cgi->param('uploaded_file'))
406 {
407 # Process file upload
408
409 my $filename = file_name($uploaded_file);
410 my $file_phys = $physical."/".$filename;
411 my $file_virt = $virtual."".$filename;
412
413 return error($config->{'err_file_exists'},$virtual,{FILE => $file_virt}) if(-e $file_phys);
414
415 my $ascii = $cgi->param('ascii');
416 my $handle = $cgi->upload('uploaded_file');
417
418 local *FILE;
419
420 open(FILE,">$file_phys") or return error($config->{'err_mkfile_failed'},$virtual,{FILE => $file_virt});
421 binmode(FILE) unless($ascii);
422
423 my $data;
424
425 while(read($handle,$data,1024))
426 {
427 $data =~ s/\015\012|\012|\015/\n/g if($ascii);
428 print FILE $data;
429 }
430
431 close(FILE);
432
433 return devedit_reload({command => "show", file => $virtual});
434 }
435 else
436 {
437 my $tpl = new Template;
438 $tpl->read_file($config->{'tpl_upload'});
439
440 $tpl->fillin("DIR",$virtual);
441 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
442 $tpl->fillin("SCRIPT",$script);
443
444 my $output = header(-type => "text/html");
445 $output .= $tpl->get_template;
446
447 return \$output;
448 }
449 }
450
451 # exec_copy()
452 #
453 # Copy a file and return to directory view
454 #
455 # Params: 1. Reference to user input hash
456 # 2. Reference to config hash
457 #
458 # Return: Output of the command (Scalar Reference)
459
460 sub exec_copy($$)
461 {
462 my ($data,$config) = @_;
463 my $physical = $data->{'physical'};
464 my $virtual = encode_entities($data->{'virtual'});
465 my $new_physical = $data->{'new_physical'};
466
467 return error($config->{'err_nocopy'}) unless(-r $physical);
468
469 if($new_physical)
470 {
471 my $new_virtual = $data->{'new_virtual'};
472 my $dir = upper_path($new_virtual);
473 $new_virtual = encode_entities($new_virtual);
474
475 if(-e $new_physical)
476 {
477 return error($config->{'err_exist_edited'},$dir,{FILE => $new_virtual}) if($data->{'uselist'}->in_use($data->{'new_virtual'}));
478
479 if(-d $new_physical)
480 {
481 return error($config->{'err_dircopy'});
482 }
483 elsif(not $data->{'cgi'}->param('confirmed'))
484 {
485 my $tpl = new Template;
486 $tpl->read_file($config->{'tpl_confirm_replace'});
487
488 $tpl->fillin("FILE",$virtual);
489 $tpl->fillin("NEW_FILE",$new_virtual);
490 $tpl->fillin("NEW_FILENAME",file_name($new_virtual));
491 $tpl->fillin("NEW_DIR",$dir);
492 $tpl->fillin("DIR",upper_path($virtual));
493
494 $tpl->fillin("COMMAND","copy");
495 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
496 $tpl->fillin("SCRIPT",$script);
497
498 my $output = header(-type => "text/html");
499 $output .= $tpl->get_template;
500
501 return \$output;
502 }
503 }
504
505 copy($physical,$new_physical) or return error($config->{'err_copy_failed'},upper_path($virtual),{FILE => $virtual, NEW_FILE => $new_virtual});
506 return devedit_reload({command => 'show', file => $dir});
507 }
508 else
509 {
510 my $tpl = new Template;
511 $tpl->read_file($config->{'tpl_copyfile'});
512
513 $tpl->fillin("FILE",$virtual);
514 $tpl->fillin("DIR",upper_path($virtual));
515 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
516 $tpl->fillin("SCRIPT",$script);
517
518 my $output = header(-type => "text/html");
519 $output .= $tpl->get_template;
520
521 return \$output;
522 }
523 }
524
525 # exec_rename()
526 #
527 # Rename/move a file and return to directory view
528 #
529 # Params: 1. Reference to user input hash
530 # 2. Reference to config hash
531 #
532 # Return: Output of the command (Scalar Reference)
533
534 sub exec_rename($$)
535 {
536 my ($data,$config) = @_;
537 my $physical = $data->{'physical'};
538 my $virtual = $data->{'virtual'};
539 my $new_physical = $data->{'new_physical'};
540
541 return error($config->{'err_in_use'},upper_path($virtual),{FILE => $virtual}) if($data->{'uselist'}->in_use($virtual));
542
543 if($new_physical)
544 {
545 my $new_virtual = $data->{'new_virtual'};
546 my $dir = upper_path($new_virtual);
547 $new_virtual = encode_entities($new_virtual);
548
549 if(-e $new_physical)
550 {
551 return error($config->{'err_exist_edited'},$dir,{FILE => $new_virtual}) if($data->{'uselist'}->in_use($data->{'new_virtual'}));
552
553 if(-d $new_physical)
554 {
555 return error($config->{'err_dircopy'});
556 }
557 elsif(not $data->{'cgi'}->param('confirmed'))
558 {
559 my $tpl = new Template;
560 $tpl->read_file($config->{'tpl_confirm_replace'});
561
562 $tpl->fillin("FILE",$virtual);
563 $tpl->fillin("NEW_FILE",$new_virtual);
564 $tpl->fillin("NEW_FILENAME",file_name($new_virtual));
565 $tpl->fillin("NEW_DIR",$dir);
566 $tpl->fillin("DIR",upper_path($virtual));
567
568 $tpl->fillin("COMMAND","rename");
569 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
570 $tpl->fillin("SCRIPT",$script);
571
572 my $output = header(-type => "text/html");
573 $output .= $tpl->get_template;
574
575 return \$output;
576 }
577 }
578
579 rename($physical,$new_physical) or return error($config->{'err_rename_failed'},upper_path($virtual),{FILE => $virtual, NEW_FILE => $new_virtual});
580 return devedit_reload({command => 'show', file => $dir});
581 }
582 else
583 {
584 my $tpl = new Template;
585 $tpl->read_file($config->{'tpl_renamefile'});
586
587 $tpl->fillin("FILE",$virtual);
588 $tpl->fillin("DIR",upper_path($virtual));
589 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
590 $tpl->fillin("SCRIPT",$script);
591
592 my $output = header(-type => "text/html");
593 $output .= $tpl->get_template;
594
595 return \$output;
596 }
597 }
598
599 # exec_remove()
600 #
601 # Remove a file or a directory and return to directory view
602 #
603 # Params: 1. Reference to user input hash
604 # 2. Reference to config hash
605 #
606 # Return: Output of the command (Scalar Reference)
607
608 sub exec_remove($$)
609 {
610 my ($data,$config) = @_;
611 my $physical = $data->{'physical'};
612 my $virtual = $data->{'virtual'};
613
614 if(-d $physical)
615 {
616 # Remove a directory
617
618 if($data->{'cgi'}->param('confirmed'))
619 {
620 rmtree($physical);
621 return devedit_reload({command => 'show', file => upper_path($virtual)});
622 }
623 else
624 {
625 my $tpl = new Template;
626 $tpl->read_file($config->{'tpl_confirm_rmdir'});
627
628 $tpl->fillin("DIR",$virtual);
629 $tpl->fillin("UPPER_DIR",upper_path($virtual));
630 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
631 $tpl->fillin("SCRIPT",$script);
632
633 my $output = header(-type => "text/html");
634 $output .= $tpl->get_template;
635
636 return \$output;
637 }
638 }
639 else
640 {
641 # Remove a file
642
643 return error($config->{'err_in_use'},upper_path($virtual),{FILE => $virtual}) if($data->{'uselist'}->in_use($virtual));
644
645 if($data->{'cgi'}->param('confirmed'))
646 {
647 unlink($physical) or return error($config->{'err_delete_failed'},upper_path($virtual),{FILE => $virtual});
648 return devedit_reload({command => 'show', file => upper_path($virtual)});
649 }
650 else
651 {
652 my $tpl = new Template;
653 $tpl->read_file($config->{'tpl_confirm_rmfile'});
654
655 $tpl->fillin("FILE",$virtual);
656 $tpl->fillin("DIR",upper_path($virtual));
657 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
658 $tpl->fillin("SCRIPT",$script);
659
660 my $output = header(-type => "text/html");
661 $output .= $tpl->get_template;
662
663 return \$output;
664 }
665 }
666 }
667
668 # exec_unlock()
669 #
670 # Remove a file from the list of used files and
671 # return to directory view
672 #
673 # Params: 1. Reference to user input hash
674 # 2. Reference to config hash
675 #
676 # Return: Output of the command (Scalar Reference)
677
678 sub exec_unlock($$)
679 {
680 my ($data,$config) = @_;
681 my $virtual = $data->{'virtual'};
682
683 if($data->{'cgi'}->param('confirmed'))
684 {
685 file_unlock($data->{'uselist'},$virtual);
686 return devedit_reload({command => 'show', file => upper_path($virtual)});
687 }
688 else
689 {
690 my $tpl = new Template;
691 $tpl->read_file($config->{'tpl_confirm_unlock'});
692
693 $tpl->fillin("FILE",$virtual);
694 $tpl->fillin("DIR",upper_path($virtual));
695 $tpl->fillin("URL",equal_url($config->{'httproot'},$virtual));
696 $tpl->fillin("SCRIPT",$script);
697
698 my $output = header(-type => "text/html");
699 $output .= $tpl->get_template;
700
701 return \$output;
702 }
703 }
704
705 # exec_about()
706 #
707 # Display some information about Dev-Editor
708 #
709 # Params: 1. Reference to user input hash
710 # 2. Reference to config hash
711 #
712 # Return: Output of the command (Scalar Reference)
713
714 sub exec_about($$)
715 {
716 my ($data,$config) = @_;
717
718 my $tpl = new Template;
719 $tpl->read_file($config->{'tpl_about'});
720
721 $tpl->fillin("SCRIPT",$script);
722
723 # Dev-Editor's version number
724
725 $tpl->fillin("VERSION",$data->{'version'});
726
727 # Some path information
728
729 $tpl->fillin("SCRIPT_PHYS",$ENV{'SCRIPT_FILENAME'});
730 $tpl->fillin("CONFIG_PATH",$data->{'configfile'});
731 $tpl->fillin("FILE_ROOT",$config->{'fileroot'});
732 $tpl->fillin("HTTP_ROOT",$config->{'httproot'});
733
734 # Perl
735
736 $tpl->fillin("PERL_PROG",$^X);
737 $tpl->fillin("PERL_VER",sprintf("%vd",$^V));
738
739 # Information about the server
740
741 $tpl->fillin("HTTPD",$ENV{'SERVER_SOFTWARE'});
742 $tpl->fillin("OS",$^O);
743 $tpl->fillin("TIME",strftime($config->{'timeformat'},localtime));
744
745 # Process information
746
747 $tpl->fillin("PID",$$);
748
749 # Check if the functions getpwuid() and getgrgid() are available
750
751 if(eval("getpwuid(0)") && eval("getgrgid(0)"))
752 {
753 # Dev-Editor is running on a system which allows users and groups
754 # So we display the user and the group of our process
755
756 $tpl->parse_if_block("users",1);
757
758 # ID's of user and group
759
760 $tpl->fillin("UID",$<);
761 $tpl->fillin("GID",$();
762
763 # Names of user and group
764
765 $tpl->fillin("USER",getpwuid($<));
766 $tpl->fillin("GROUP",getgrgid($());
767 }
768 else
769 {
770 $tpl->parse_if_block("users",0);
771 }
772
773 my $output = header(-type => "text/html");
774 $output .= $tpl->get_template;
775
776 return \$output;
777 }
778
779 # it's true, baby ;-)
780
781 1;
782
783 #
784 ### End ###

patrick-canterino.de