File Coverage

blib/lib/WebFS/FileCopy.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package WebFS::FileCopy;
2              
3             # Copyright (C) 1998-2001 by Blair Zajac. All rights reserved. This
4             # package is free software; you can redistribute it and/or modify it
5             # under the same terms as Perl itself.
6              
7             require 5.004_04;
8              
9 1     1   11751 use strict;
  1         3  
  1         39  
10 1     1   5 use Exporter;
  1         2  
  1         66  
11 1     1   5 use Carp qw(croak cluck);
  1         7  
  1         80  
12 1     1   6 use Cwd;
  1         2  
  1         72  
13 1     1   6 use URI 1.09;
  1         31  
  1         26  
14 1     1   844 use URI::file;
  1         6178  
  1         37  
15 1     1   1897 use LWP::Version 0.24;
  0            
  0            
16             use LWP::UA 1.30;
17             use LWP::MainLoop qw(mainloop);
18             use LWP::Conn::HTTP;
19             use LWP::Conn::FTP;
20             use LWP::Request;
21             use HTTP::Request::Common 1.16 qw(GET PUT);
22             use Net::FTP 2.56;
23             use WebFS::FileCopy::Put;
24              
25             use vars qw(@EXPORT @ISA $VERSION $ua $WARN_DESTROY);
26              
27             @EXPORT = qw(©_url ©_urls &delete_urls &get_urls &list_url
28             &move_url &put_urls);
29             @ISA = qw(Exporter);
30             $VERSION = substr q$Revision: 1.04 $, 10;
31              
32             # To allow debugging of object destruction, setting WARN_DESTORY to 1
33             # till have DESTROY methods print a message when a object is destroyed.
34             $WARN_DESTROY = 0;
35              
36             # Unless the data_cb and done_cb elements of a LWP::Request object are
37             # deleted after use, the all objects using them will not DESTROY till the
38             # end of execution of the program. Use this subroutine to remove these
39             # elements.
40             sub _cleanup_requests {
41             foreach my $get_req (@_) {
42             next unless $get_req;
43             delete $get_req->{data_cb};
44             delete $get_req->{done_cb};
45             }
46             }
47              
48             package WebFS::FileCopy::UA;
49             use base 'LWP::UA';
50             use LWP::MainLoop qw(mainloop);
51             use Carp qw(cluck);
52             sub _start_read_request {
53             my ($self, $req) = @_;
54              
55             bless $req, 'LWP::Request' if ref($req) eq 'HTTP::Request';
56              
57             my $res;
58             $req->{data_cb} = sub {
59             $res = $_[1];
60             $res->add_content($_[0]);
61             };
62             $req->{done_cb} = sub {
63             $res = shift;
64             $res->{done}++;
65             };
66              
67             $self->spool($req);
68              
69             mainloop->one_event until $res || mainloop->empty;
70              
71             bless $res, 'WebFS::FileCopy::Response';
72             }
73              
74             sub _start_transfer_request {
75             my $self = shift;
76              
77             unless (@_ > 1) {
78             cluck "WebFS::FileCopy::_start_transfer_request passed two few arguments";
79             return;
80             }
81              
82             # Create and submit the GET request.
83             my $get_req = shift;
84             my $get_res = $self->_start_read_request($get_req);
85              
86             my @put_req = @_;
87              
88             # Check the response.
89             return $get_res unless $get_res->is_success;
90              
91             # This array holds the file: or ftp: objects that support print and
92             # close methods on the outgoing data. If the put fails, then hold the
93             # response placed into $@. Keep track that the responses for each PUT
94             # are in the same order as the requests.
95             my @put_connections = ();
96             my @put_res = ();
97             my $i = 0;
98             foreach my $put_req (@put_req) {
99             my $conn = WebFS::FileCopy::Put->new($put_req);
100             if ($conn) {
101             $put_connections[$i] = $conn;
102             $put_res[$i] = undef;
103             } else {
104             $put_connections[$i] = undef;
105             $put_res[$i] = $@;
106             }
107             ++$i;
108             }
109              
110             # This subroutine writes the current get contents to the output handles.
111             my $print_sub = sub {
112             my $get_res = shift;
113             my $buffer = $get_res->content('');
114             return unless length($buffer);
115             foreach my $put_conn (@put_connections) {
116             next unless $put_conn;
117             $put_conn->print($buffer);
118             }
119             };
120              
121             my $data_cb_sub = sub {
122             $get_res = $_[1];
123             $get_res->add_content($_[0]);
124             &$print_sub($get_res);
125             };
126              
127             my $done_cb_sub = sub {
128             $get_res = shift;
129             $get_res->{done}++;
130             &$print_sub($get_res);
131             # Add the HTTP::Response for closing each put.
132             my $i = -1;
133             foreach my $put_conn (@put_connections) {
134             ++$i;
135             next unless $put_conn;
136             $put_res[$i] = $put_conn->close;
137             }
138             $get_res->{put_requests} = \@put_res;
139             };
140              
141             # Update the callbacks to handle the new data transfer.
142             $get_req->{data_cb} = $data_cb_sub;
143             $get_req->{done_cb} = $done_cb_sub;
144              
145             # The gets may already be completed at this point. If this is so, then
146             # send the data to the outgoing URIs and close up.
147             &$done_cb_sub($get_res) if exists($get_res->{done});
148              
149             $get_res;
150             }
151              
152             sub DESTROY {
153             if ($WebFS::FileCopy::WARN_DESTROY) {
154             my $self = shift;
155             print STDERR "DESTROYing $self\n";
156             }
157             }
158              
159             package WebFS::FileCopy::Response;
160             use base 'HTTP::Response';
161             use LWP::MainLoop qw(mainloop);
162              
163             sub _read_content {
164             my $self = shift;
165              
166             my $c = $self->content('');
167             return $c if length($c);
168              
169             return if $self->{done};
170              
171             # Now wait for more data.
172             my $data;
173             $self->request->{data_cb} = sub { $data = $_[0]; };
174             mainloop->one_event until
175             mainloop->empty || defined($data) || $self->{done};
176              
177             $data;
178             }
179              
180             sub DESTROY {
181             if ($WebFS::FileCopy::WARN_DESTROY) {
182             my $self = shift;
183             print STDERR "DESTROYing $self\n";
184             }
185             }
186              
187             package WebFS::FileCopy;
188              
189             sub _init_ua {
190             # Create a global UserAgent object.
191             $ua = WebFS::FileCopy::UA->new;
192             $ua->env_proxy;
193             }
194              
195             # Take either a string, a URI, a HTTP::Request, a LWP::Request and make
196             # it into an absolute URI. Do not touch the given URI. If the URI is
197             # missing a scheme, then assume it to be file and if the file path is
198             # not an absolute one, then assume that the current directory contains
199             # the file.
200             sub _create_uri {
201             my ($uri, $base) = @_;
202              
203             # Handle the URI differently if it is a string or an object. If the uri
204             # is an object, then check if it is a HTTP::Request or a child of that
205             # class and take the URI from that object. Now we have a URI object and
206             # make sure it is canonicalized, since with a URI like http://www.a1.com
207             # the path will be undefined.
208             if (ref($uri)) {
209             $uri = $uri->uri if $uri->isa('HTTP::Request');
210             $uri = $uri->clone;
211             $uri = $uri->abs($base) if defined($base) && $base;
212             $uri = $uri->canonical;
213             } else {
214             my $temp = $uri;
215             if (defined($base) and $base) {
216             $uri = eval { URI->new_abs($uri, $base)->canonical; };
217             } else {
218             $uri = eval { URI->new($uri)->canonical; };
219             }
220             cluck "WebFS::FileCopy::_create_uri failed on $temp: $@" if $@;
221             }
222             $uri;
223             }
224              
225             # Take a request method (POST, GET, etc) and either a string URI, a URI
226             # object, a HTTP::Request or subclass of HTTP::Request object such as
227             # LWP::Request. Make use of _create_uri if the URI is not a HTTP::Request
228             # type. If it is a HTTP::Request make a clone and work with that.
229             sub _create_request {
230             my ($method, $uri, $base) = @_;
231              
232             if (ref($uri) and $uri->isa('HTTP::Request')) {
233             # Recase the object into a LWP::Request and make sure the method
234             # is the request type.
235             $uri = bless $uri->clone, 'LWP::Request';
236             $uri->method($method);
237             return $uri;
238             } else {
239             return LWP::Request->new($method, _create_uri($uri, $base));
240             }
241             }
242              
243             # Take a URI and return if the URI is a directory or a file. A directory
244             # always ends in /.
245             sub _is_directory {
246             my ($uri, $base) = @_;
247              
248             $uri = _create_uri($uri, $base);
249              
250             return $uri ? ($uri->path =~ m:/$:) : undef;
251             }
252              
253             sub get_urls {
254             return () unless @_;
255              
256             my @uris = @_;
257              
258             _init_ua unless $ua;
259              
260             # Quickly spool each GET request.
261             my @get_req = ();
262             my @get_res = ();
263             my $i = 0;
264             foreach my $uri (@uris) {
265             my $get_req = _create_request('GET', $uri);
266              
267             # $j is created here to be local to this loop and recorded in each
268             # anonymous subroutine created below.
269             my $j = $i;
270              
271             $get_res[$j] = undef;
272             $get_req->{data_cb} = sub {
273             $get_res[$j] = $_[1];
274             $get_res[$j]->add_content($_[0]);
275             };
276             $get_req->{done_cb} = sub {
277             $get_res[$j] = shift;
278             $get_res[$j]->{done}++;
279             };
280             $ua->spool($get_req);
281             $get_req[$j] = $get_req;
282             ++$i;
283             }
284              
285             # Perform one_event() until all of the done requests are handled.
286             while (1) {
287             my $done = 1;
288             foreach my $get_res (@get_res) {
289             unless (defined($get_res) and exists($get_res->{done})) {
290             $done = 0;
291             last;
292             }
293             }
294             last if $done || mainloop->empty;
295             mainloop->one_event;
296             }
297              
298             # Allow garbage collection to happen.
299             _cleanup_requests(@get_req);
300              
301             # Return the responses.
302             @get_res;
303             }
304              
305             sub put_urls {
306             unless (@_ >= 2) {
307             $@ = 'Too few arguments';
308             cluck $@;
309             return;
310             }
311              
312             my $string_or_code = shift;
313              
314             # Convert string URIs to LWP::Requests.
315             my @put_reqs = map { _create_request('PUT', $_) } @_;
316              
317             # This holds the responses for each PUT request.
318             my @put_res = ();
319              
320             # Go through each URI and create a request for it if the URI is ok.
321             my @put_req = ();
322             my $leave_now = 1;
323             foreach my $put_req (@put_reqs) {
324             my $uri = $put_req->uri;
325              
326             # We put this in so that give_response can be used.
327             $put_req->{done_cb} = sub { $_[0]; };
328              
329             # Need a valid URI.
330             unless ($uri) {
331             push(@put_req, 0);
332             push(@put_res,
333             $put_req->give_response(400, 'Missing URL in request'));
334             next;
335             }
336              
337             # URI cannot be a directory.
338             if (_is_directory($uri)) {
339             push(@put_req, 0);
340             push(@put_res,
341             $put_req->give_response(403, 'URL cannot be a directory'));
342             next;
343             }
344              
345             # URI scheme needs to be either ftp or file.
346             my $scheme = $uri->scheme;
347             unless ($scheme && ($scheme eq 'ftp' or $scheme eq 'file')) {
348             push(@put_req, 0);
349             push(@put_res,
350             $put_req->give_response(400, "Invalid scheme $scheme"));
351             next;
352             }
353              
354             # We now have a valid request.
355             push(@put_req, $put_req);
356             push(@put_res, $put_req->give_response(201));
357             $leave_now = 0;
358             }
359              
360             # Leave now if there are no valid requests. @put_req contains 0's for
361             # each invalid URI.
362             if ($leave_now) {
363             # Allow garbage collection to happen.
364             _cleanup_requests(@put_req);
365             return @put_res;
366             }
367              
368             _init_ua unless $ua;
369              
370             # For each valid PUT request, create the connection.
371             my @put_connections = ();
372             my $i = 0;
373             foreach my $put_req (@put_req) {
374             my $conn;
375             if ($put_req) {
376             $conn = WebFS::FileCopy::Put->new($put_req);
377             # If the connection cannot be created, then get the response from $@.
378             $put_res[$i] = $@ unless $conn;
379             }
380             push(@put_connections, $conn);
381             ++$i;
382             }
383              
384             # Push the data to each valid connection. For the CODE reference,
385             # call it until it returns undef or ''.
386             if (ref($string_or_code) eq 'CODE') {
387             my $buffer;
388             while (defined($buffer = &$string_or_code) and length($buffer)) {
389             foreach my $conn (@put_connections) {
390             next unless $conn;
391             $conn->print($buffer);
392             }
393             }
394             } else {
395             foreach my $conn (@put_connections) {
396             next unless $conn;
397             $conn->print($string_or_code);
398             }
399             }
400              
401             # Close the connection and hold onto the close status.
402             $i = 0;
403             foreach my $put_conn (@put_connections) {
404             if ($put_conn) {
405             $put_res[$i] = $put_conn->close;
406             }
407             ++$i;
408             }
409              
410             # Allow garbage collection to happen.
411             _cleanup_requests(@put_req);
412              
413             @put_res;
414             }
415              
416             sub copy_urls {
417             unless (@_ == 2 or @_ == 3) {
418             $@ = 'Incorrect number of arguments';
419             cluck $@;
420             return;
421             }
422              
423             my ($from_input, $to_input, $base) = @_;
424              
425             # Create the arrays holding the to and from locations using either the
426             # array references or the single URIs.
427             my @from = ref($from_input) eq 'ARRAY' ? @$from_input : ($from_input);
428             my @to = ref($to_input) eq 'ARRAY' ? @$to_input : ($to_input);
429              
430             # Convert string URIs to LWP::Requests.
431             @from = map { _create_request('GET', $_, $base) } @from;
432             @to = map { _create_request('PUT', $_, $base) } @to;
433              
434             my $number_valid_froms = grep($_->uri, @from);
435             my $number_valid_tos = grep($_->uri, @to);
436              
437             # We ignore empty URIs, but make sure there are some URIs.
438             unless ($number_valid_froms) {
439             $@ = 'No non-empty GET URLs';
440             return;
441             }
442              
443             unless ($number_valid_tos) {
444             $@ = 'No non-empty PUT URLs';
445             return;
446             }
447              
448             # Check that the to destination URIs are either file: or ftp:.
449             foreach my $put_req (@to) {
450             # Skip empty requests.
451             my $uri = $put_req->uri;
452             next unless $uri;
453             my $scheme = $uri->scheme;
454             unless ($scheme && ($scheme eq 'ftp' or $scheme eq 'file')) {
455             $@ = "Can only copy to file or FTP URLs: " . $uri;
456             return;
457             }
458             }
459              
460             # All of the from URIs must be non-directories.
461             foreach my $get_req (@from) {
462             my $uri = $get_req->uri;
463             if ($uri and _is_directory($uri)) {
464             $@ = "Cannot copy directories: " . $uri;
465             return;
466             }
467             }
468              
469             # If any of the destination URIs is a file, then there can only be
470             # one source URI.
471             if ($number_valid_froms > 1) {
472             foreach my $put_req (@to) {
473             my $uri = $put_req->uri;
474             next unless $uri;
475             if (!_is_directory($uri)) {
476             $@ = 'Cannot copy many files to one file';
477             return;
478             }
479             }
480             }
481              
482             _init_ua unless $ua;
483              
484             # Set up the transfer between the from and to URIs.
485             my @get_res = ();
486             foreach my $get_req (@from) {
487             my $from_uri = $get_req->uri;
488              
489             # If the from URI is empty, then generate a missing URI response.
490             unless ($from_uri) {
491             $get_req->{done_cb} = sub { $_[0]; };
492             push(@get_res, $get_req->give_response(400, 'Missing URL in request'));
493             next;
494             }
495              
496             # Do not generate the put requests if this is an empty from URI.
497             my @put_req = ();
498              
499             foreach (@to) {
500             my $put_req = $_->clone;
501             my $to_uri = $put_req->uri;
502             # If the to URI is a directory, then copy the filename from the
503             # from URI to the to URI.
504             if (_is_directory($to_uri)) {
505             my @from_path = split(/\//, $from_uri->path);
506             $to_uri->path($to_uri->path . $from_path[$#from_path]);
507             $put_req->uri($to_uri);
508             }
509              
510             # Put together a put request using the output from a get request.
511             push(@put_req, $put_req);
512             }
513             my $get_res = $ua->_start_transfer_request($get_req, @put_req);
514             push(@get_res, $get_res) if $get_res;
515             }
516              
517             # Loop until all of the data is transfered.
518             while (1) {
519             my $done = 1;
520             foreach my $get_res (@get_res) {
521             next unless $get_res->is_success;
522             $done &&= exists($get_res->{put_requests});
523             }
524             last if $done || mainloop->empty;
525             mainloop->one_event;
526             }
527              
528             # Allow garbage collection to happen.
529             _cleanup_requests(@from, @to);
530              
531             @get_res;
532             }
533              
534             # Print a status summary using the return from copy_urls.
535             sub _dump {
536             my $fd = (ref($_[0]) || $_[0] =~ /^\*[\w:]+\w$/) ? shift : 'STDOUT';
537             foreach my $get_res (@_) {
538             my $uri = $get_res->request->uri;
539             print $fd "GET from $uri ";
540             unless ($get_res->is_success) {
541             print $fd "FAILED ", $get_res->message, "\n";
542             next;
543             }
544              
545             print $fd "SUCCEEDED\n";
546             foreach my $c (@{$get_res->{put_requests}}) {
547             $uri = $c->request->uri;
548             if ($c->is_success) {
549             print $fd " to $uri succeeded\n"
550             } else {
551             print $fd " to $uri failed: ", $c->message, "\n";
552             }
553             }
554             }
555             }
556              
557             sub copy_url {
558             unless (@_ == 2 or @_ == 3) {
559             $@ = 'Incorrect number of arguments';
560             cluck $@;
561             return;
562             }
563              
564             my ($from, $to, $base) = @_;
565              
566             # Convert string URIs to URIs.
567             $from = _create_request('GET', $from, $base);
568             $to = _create_request('PUT', $to, $base);
569              
570             # Check for valid URIs.
571             unless ($from->uri) {
572             $@ = 'Missing GET URL';
573             return;
574             }
575              
576             unless ($to->uri) {
577             $@ = 'Missing PUT URL';
578             return;
579             }
580              
581             # Run the real copy_urls and get the return value.
582             my @ret = copy_urls($from, $to, $base);
583             return unless @ret;
584              
585             my $get_res = shift(@ret);
586             unless ($get_res->is_success) {
587             $@ = 'GET ' . $get_res->request->uri . ': ' . $get_res->message;
588             return 0;
589             }
590             my @put_res = @{$get_res->{put_requests}};
591              
592             # This should never happen.
593             unless (@put_res) {
594             $@ = 'Found a bug: no returned PUT requests from copy_urls';
595             cluck $@;
596             return;
597             }
598              
599             # Check each PUT request.
600             foreach my $put_res (@put_res) {
601             unless ($put_res->is_success) {
602             $@ = 'PUT ' . $put_res->request->uri . ': ' . $put_res->message;
603             return 0;
604             }
605             }
606             1;
607             }
608              
609             sub delete_urls {
610             my @uris = @_;
611              
612             return () unless @uris;
613              
614             _init_ua unless $ua;
615              
616             # Go through each URI, create a request, and spool it.
617             my @del_req = ();
618             my @del_res = ();
619             my $i = 0;
620             foreach my $uri (@uris) {
621             my $del_req = _create_request('DELETE', $uri);
622              
623             # $j is created here to be local to this loop and recorded in each
624             # anonymous subroutine created below.
625             my $j = $i;
626              
627             $del_res[$j] = undef;
628             $del_req->{done_cb} = sub { $del_res[$j] = shift; };
629             $ua->spool($del_req);
630             $del_req[$j] = $del_req;
631             ++$i;
632             }
633              
634             # Perform one_event until all of the done requests are handled.
635             while (1) {
636             my $done = 1;
637             foreach my $del_res (@del_res) {
638             unless (defined($del_res)) {
639             $done = 0;
640             last;
641             }
642             }
643             last if $done || mainloop->empty;
644             mainloop->one_event;
645             }
646              
647             # Allow garbage collection to happen.
648             _cleanup_requests(@del_req);
649              
650             # Return the status.
651             @del_res;
652             }
653              
654             sub move_url {
655             unless (@_ == 2 or @_ == 3) {
656             $@ = 'Incorrect number of arguments';
657             cluck $@;
658             return;
659             }
660              
661             my ($from, $to, $base) = @_;
662              
663             # Convert string URIs to URIs.
664             $from = _create_request('GET', $from, $base);
665             $to = _create_request('PUT', $to, $base);
666              
667             # Copy the URI. Make sure to pass down $@ failures from copy_url.
668             if (copy_url($from, $to)) {
669             my @ret = delete_urls($from);
670             my $ret = $ret[0];
671             if ($ret->is_success) {
672             return 1;
673             } else {
674             $@ = $ret->message;
675             return 0;
676             }
677             } else {
678             return 0;
679             }
680             }
681              
682             sub _list_file_uri {
683             my $uri = shift;
684              
685             # Check that the host is ok.
686             my $host = $uri->host;
687             if ($host and $host !~ /^localhost$/i) {
688             $@ = 'Only file://localhost/ allowed';
689             return;
690             }
691              
692             # Get file path.
693             my $path = $uri->file;
694              
695             # Check that the directory exists and is readable.
696             unless (-e $path) {
697             $@ = "File or directory `$path' does not exist";
698             return;
699             }
700             unless (-r _) {
701             $@ = "User does not have read permission for `$path'";
702             return;
703             }
704             unless (-d _) {
705             $@ = "Path `$path' is not a directory";
706             return;
707             }
708              
709             # List the directory.
710             unless (opendir(D, $path)) {
711             $@ = "Cannot read directory `$path': $!";
712             return;
713             }
714              
715             my @listing = sort readdir(D);
716              
717             closedir(D) or
718             print STDERR "$0: error in closing directory `$path': $!\n";
719              
720             @listing;
721             }
722              
723             sub _list_ftp_uri {
724             my $uri = shift;
725              
726             my $req = _create_request('GET', $uri);
727             $req->{done_cb} = sub { $_[0] };
728             my $ftp = _open_ftp_connection($req);
729             unless ($ftp) {
730             $@ = $@->message;
731             return;
732             }
733              
734             # Get and fix path.
735             my @path = $uri->path_segments;
736             # There will always be an empty first component.
737             shift(@path);
738             # Remove the empty trailing components.
739             pop(@path) while @path && $path[-1] eq '';
740              
741             # Change directories.
742             foreach my $dir (@path) {
743             unless ($ftp->cwd($dir)) {
744             $@ = "Cannot chdir to `$dir'";
745             return;
746             }
747             }
748              
749             # Now get a listing.
750             my @listing = $ftp->ls;
751              
752             # Close the connection.
753             $ftp->quit;
754              
755             @listing;
756             }
757              
758             sub list_url {
759             my $uri = shift;
760              
761             $uri = _create_uri($uri);
762             unless ($uri) {
763             $@ = "Missing URL";
764             return;
765             }
766              
767             my $scheme = $uri->scheme;
768             unless ($scheme) {
769             $@ = "Missing scheme in URL $uri";
770             return;
771             }
772              
773             if ($scheme eq 'file' || $scheme eq 'ftp' ) {
774             my $code = "_list_${scheme}_uri";
775             no strict 'refs';
776             my @listing = &$code($uri);
777             if (@listing) {
778             return @listing;
779             } else {
780             return;
781             }
782             } else {
783             $@ = "Unsupported scheme $scheme in URL $uri";
784             return;
785             }
786             }
787              
788             # Open a FTP connection. Return either a Net::FTP object or undef if
789             # failes. If somethings fails, then $@ will hold a HTTP::Response
790             # object.
791             sub _open_ftp_connection {
792             my $req = shift;
793              
794             my $uri = $req->uri;
795             unless ($uri->scheme eq 'ftp') {
796             cluck "Use a FTP URL";
797             $@ = $req->give_response(400, "Use a FTP URL");
798             return;
799             }
800              
801             # Handle user authentication. If the username, password and/or
802             # account is not set, then Net::FTP will attempt to set these
803             # properly, so there's no point in doing that here.
804             my ($user, $pass) = $req->authorization_basic;
805             $user ||= $uri->user;
806             $pass ||= $uri->password;
807             my $acct = $req->header('Account');
808              
809             # Open the initial connection.
810             my $ftp = Net::FTP->new($uri->host);
811             unless ($ftp) {
812             $@ =~ s/^Net::FTP: //;
813             $@ = $req->give_response(500, $@);
814             return;
815             }
816              
817             # Try to log in.
818             unless ($ftp->login($user, $pass, $acct)) {
819             # Unauthorized access. Fake a RC_UNAUTHORIZED response.
820             $@ = $req->give_response(401, $ftp->message);
821             $@->header("WWW-Authenticate", qq(Basic Realm="FTP login"));
822             return;
823             }
824              
825             # Switch to ASCII or binary mode.
826             if ($uri =~ /type=a/i) {
827             $ftp->ascii;
828             } else {
829             $ftp->binary;
830             }
831              
832             $ftp;
833             }
834              
835             1;
836              
837             __END__