home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / perl_ste.zip / LWP / Protocol / ftp.pm < prev    next >
Text File  |  1997-01-23  |  13KB  |  377 lines

  1. #
  2. # $Id: ftp.pm,v 1.19 1997/01/23 21:38:59 aas Exp $
  3.  
  4. # Implementation of the ftp protocol (RFC 959). We let the Net::FTP
  5. # package do all the dirty work.
  6.  
  7. package LWP::Protocol::ftp;
  8.  
  9. use Carp ();
  10.  
  11. use HTTP::Status ();
  12. use HTTP::Negotiate ();
  13. use HTTP::Response ();
  14. use LWP::MediaTypes ();
  15. use File::Listing ();
  16.  
  17. require LWP::Protocol;
  18. @ISA = qw(LWP::Protocol);
  19.  
  20. use strict;
  21. eval {
  22.     require Net::FTP;
  23.     Net::FTP->require_version(2.00);
  24. };
  25. my $init_failed = $@;
  26.  
  27.  
  28. sub request
  29. {
  30.     my($self, $request, $proxy, $arg, $size, $timeout) = @_;
  31.  
  32.     $size = 4096 unless $size;
  33.  
  34.     LWP::Debug::trace('()');
  35.  
  36.     # check proxy
  37.     if (defined $proxy)
  38.     {
  39.     return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
  40.                   'You can not proxy through the ftp';
  41.     }
  42.  
  43.     my $url = $request->url;
  44.     if ($url->scheme ne 'ftp') {
  45.     my $scheme = $url->scheme;
  46.     return new HTTP::Response &HTTP::Status::RC_INTERNAL_SERVER_ERROR,
  47.                "LWP::Protocol::ftp::request called for '$scheme'";
  48.     }
  49.  
  50.     # check method
  51.     my $method = $request->method;
  52.  
  53.     unless ($method eq 'GET' || $method eq 'HEAD' || $method eq 'PUT') {
  54.     return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
  55.                   'Library does not allow method ' .
  56.                   "$method for 'ftp:' URLs";
  57.     }
  58.  
  59.     if ($init_failed) {
  60.     return new HTTP::Response &HTTP::Status::RC_INTERNAL_SERVER_ERROR,
  61.                $init_failed;
  62.     }
  63.  
  64.     my $host     = $url->host;
  65.     my $port     = $url->port;
  66.     my $user     = $url->user;
  67.     my $password = $url->password;
  68.  
  69.     # If a basic autorization header is present than we prefer these over
  70.     # the username/password specified in the URL.
  71.     {
  72.     my($u,$p) = $request->authorization_basic;
  73.     if (defined $u) {
  74.         $user = $u;
  75.         $password = $p;
  76.     }
  77.     }
  78.  
  79.     # We allow the account to be specified in the "Account" header
  80.     my $acct     = $request->header('Account');
  81.  
  82.     # Create an initial response object
  83.     my $response = new HTTP::Response &HTTP::Status::RC_OK, "Document follows";
  84.     $response->request($request);
  85.  
  86.     my $ftp = new Net::FTP $host, Port => $port;
  87.     my $mess = $ftp->message;  # welcome message
  88.     LWP::Debug::debug($mess);
  89.     $mess =~ s|\n.*||s; # only first line left
  90.     $mess =~ s|\s*ready\.?$||;
  91.     # Make the version number more HTTP like
  92.     $mess =~ s|\s*\(Version\s*|/| and $mess =~ s|\)$||;
  93.     $response->header("Server", $mess);
  94.  
  95.     $ftp->timeout($timeout) if $timeout;
  96.  
  97.     LWP::Debug::debug("Logging in as $user (password $password)...");
  98.     unless ($ftp->login($user, $password, $acct)) {
  99.     # Unauthorized.  Let's fake a RC_UNAUTHORIZED response
  100.     my $res =  new HTTP::Response &HTTP::Status::RC_UNAUTHORIZED, $ftp->message;
  101.     $res->header("WWW-Authenticate", qq(Basic Realm="FTP login"));
  102.     return $res;
  103.     }
  104.     LWP::Debug::debug($ftp->message);
  105.  
  106.     # Get & fix the path
  107.     my @path =  $url->path_components;
  108.     shift(@path);  # There will always be an empty first component
  109.     pop(@path) while @path && $path[-1] eq '';  # remove empty tailing comps
  110.     my $remote_file = pop(@path);
  111.     $remote_file = '' unless defined $remote_file;
  112.  
  113.     my $params = $url->params;
  114.     if (defined($params) && $params eq 'type=a') {
  115.     $ftp->ascii;
  116.     } else {
  117.     $ftp->binary;
  118.     }
  119.  
  120.     for (@path) {
  121.     LWP::Debug::debug("CWD $_");
  122.     unless ($ftp->cwd($_)) {
  123.         return new HTTP::Response &HTTP::Status::RC_NOT_FOUND,
  124.                "Can't chdir to $_";
  125.     }
  126.     }
  127.  
  128.     if ($method eq 'GET' || $method eq 'HEAD') {
  129.     my $data;  # the data handle
  130.     LWP::Debug::debug("retrieve file?");
  131.     if (length($remote_file) and $data = $ftp->retr($remote_file)) {
  132.         my($type, @enc) = LWP::MediaTypes::guess_media_type($remote_file);
  133.         $response->header('Content-Type',   $type) if $type;
  134.         for (@enc) {
  135.         $response->push_header('Content-Encoding', $_);
  136.         }
  137.         my $mess = $ftp->message;
  138.         LWP::Debug::debug($mess);
  139.         if ($mess =~ /\((\d+)\s+bytes\)/) {
  140.         $response->header('Content-Length', "$1");
  141.         }
  142.  
  143.         if ($method ne 'HEAD') {
  144.         # Read data from server
  145.         $response = $self->collect($arg, $response, sub {
  146.             my $content = '';
  147.             my $result = $data->read($content, $size);
  148.             return \$content;
  149.         } );
  150.         }
  151.         unless ($data->close) {
  152.         # Something did not work too well
  153.         if ($method ne 'HEAD') {
  154.             $response->code(&HTTP::Status::RC_INTERNAL_SERVER_ERROR);
  155.             $response->message("FTP close response: " . $ftp->code .
  156.                        " " . $ftp->message);
  157.         }
  158.         }
  159.     } elsif (!length($remote_file) || $ftp->code == 550) {
  160.         # 550 not a plain file, try to list instead
  161.         if (length($remote_file) && !$ftp->cwd($remote_file)) {
  162.         LWP::Debug::debug("chdir before listing failed");
  163.         return new HTTP::Response &HTTP::Status::RC_NOT_FOUND,
  164.                "File '$remote_file' not found";
  165.         }
  166.  
  167.         # It should now be safe to try to list the directory
  168.         LWP::Debug::debug("dir");
  169.         my @lsl = $ftp->dir;
  170.  
  171.         # Try to figure out if the user want us to convert the
  172.         # directory listing to HTML.
  173.         my @variants =
  174.           (
  175.            ['html',  0.60, 'text/html'            ],
  176.            ['dir',   1.00, 'text/ftp-dir-listing' ]
  177.           );
  178.         #$HTTP::Negotiate::DEBUG=1;
  179.         my $prefer = HTTP::Negotiate::choose(\@variants, $request);
  180.  
  181.         my $content = '';
  182.  
  183.         if (!defined($prefer)) {
  184.         return new HTTP::Response &HTTP::Status::RC_NOT_ACCEPTABLE,
  185.                    "Neither HTML nor directory listing wanted";
  186.         } elsif ($prefer eq 'html') {
  187.         $response->header('Content-Type' => 'text/html');
  188.         $content = "<HEAD><TITLE>File Listing</TITLE>\n";
  189.         my $base = $request->url->clone;
  190.         my $path = $base->epath;
  191.         $base->epath("$path/") unless $path =~ m|/$|;
  192.         $content .= qq(<BASE HREF="$base">\n</HEAD>\n);
  193.         $content .= "<BODY>\n<UL>\n";
  194.         for (File::Listing::parse_dir(\@lsl, 'GMT')) {
  195.             my($name, $type, $size, $mtime, $mode) = @$_;
  196.             $content .= qq(  <LI> <a href="$name">$name</a>);
  197.             $content .= " $size bytes" if $type eq 'f';
  198.             $content .= "\n";
  199.         }
  200.         $content .= "</UL></body>\n";
  201.         } else {
  202.         $response->header('Content-Type', 'text/ftp-dir-listing');
  203.         $content = join("\n", @lsl, '');
  204.         }
  205.  
  206.         $response->header('Content-Length', length($content));
  207.  
  208.         if ($method ne 'HEAD') {
  209.         $response = $self->collect_once($arg, $response, $content);
  210.         }
  211.     } else {
  212.         my $res = new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
  213.               "FTP return code " . $ftp->code;
  214.         $res->content_type("text/plain");
  215.         $res->content($ftp->message);
  216.         return $res;
  217.     }
  218.     } elsif ($method eq 'PUT') {
  219.     # method must be PUT
  220.     unless (length($remote_file)) {
  221.         return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
  222.                       "Must have a file name to PUT to";
  223.     }
  224.     my $data;
  225.     if ($data = $ftp->stor($remote_file)) {
  226.         LWP::Debug::debug($ftp->message);
  227.         LWP::Debug::debug("$data");
  228.         my $content = $request->content;
  229.         my $bytes = 0;
  230.         if (defined $content) {
  231.         if (ref($content) eq 'SCALAR') {
  232.             $bytes = $data->write($$content, length($$content));
  233.         } elsif (ref($content) eq 'CODE') {
  234.             my($buf, $n);
  235.             while (length($buf = &$content)) {
  236.             $n = $data->write($buf, length($buf));
  237.             last unless $n;
  238.             $bytes += $n;
  239.             }
  240.         } elsif (!ref($content)) {
  241.             if (defined $content && length($content)) {
  242.             $bytes = $data->write($content, length($content));
  243.             }
  244.         } else {
  245.             die "Bad content";
  246.         }
  247.         }
  248.         $data->close;
  249.         LWP::Debug::debug($ftp->message);
  250.  
  251.         $response->code(&HTTP::Status::RC_CREATED);
  252.         $response->header('Content-Type', 'text/plain');
  253.         $response->content("$bytes bytes stored as $remote_file on $host\n")
  254.  
  255.     } else {
  256.         my $res = new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
  257.               "FTP return code " . $ftp->code;
  258.         $res->content_type("text/plain");
  259.         $res->content($ftp->message);
  260.         return $res;
  261.     }
  262.     } else {
  263.     return new HTTP::Response &HTTP::Status::RC_BAD_REQUEST,
  264.            "Illegal method $method"
  265.     }
  266.  
  267.     $response;
  268. }
  269.  
  270. 1;
  271.  
  272. __END__
  273.  
  274. # This is what RFC 1738 has to say about FTP access:
  275. # --------------------------------------------------
  276. #
  277. # 3.2. FTP
  278. #
  279. #    The FTP URL scheme is used to designate files and directories on
  280. #    Internet hosts accessible using the FTP protocol (RFC959).
  281. #
  282. #    A FTP URL follow the syntax described in Section 3.1.  If :<port> is
  283. #    omitted, the port defaults to 21.
  284. #
  285. # 3.2.1. FTP Name and Password
  286. #
  287. #    A user name and password may be supplied; they are used in the ftp
  288. #    "USER" and "PASS" commands after first making the connection to the
  289. #    FTP server.  If no user name or password is supplied and one is
  290. #    requested by the FTP server, the conventions for "anonymous" FTP are
  291. #    to be used, as follows:
  292. #
  293. #         The user name "anonymous" is supplied.
  294. #
  295. #         The password is supplied as the Internet e-mail address
  296. #         of the end user accessing the resource.
  297. #
  298. #    If the URL supplies a user name but no password, and the remote
  299. #    server requests a password, the program interpreting the FTP URL
  300. #    should request one from the user.
  301. #
  302. # 3.2.2. FTP url-path
  303. #
  304. #    The url-path of a FTP URL has the following syntax:
  305. #
  306. #         <cwd1>/<cwd2>/.../<cwdN>/<name>;type=<typecode>
  307. #
  308. #    Where <cwd1> through <cwdN> and <name> are (possibly encoded) strings
  309. #    and <typecode> is one of the characters "a", "i", or "d".  The part
  310. #    ";type=<typecode>" may be omitted. The <cwdx> and <name> parts may be
  311. #    empty. The whole url-path may be omitted, including the "/"
  312. #    delimiting it from the prefix containing user, password, host, and
  313. #    port.
  314. #
  315. #    The url-path is interpreted as a series of FTP commands as follows:
  316. #
  317. #       Each of the <cwd> elements is to be supplied, sequentially, as the
  318. #       argument to a CWD (change working directory) command.
  319. #
  320. #       If the typecode is "d", perform a NLST (name list) command with
  321. #       <name> as the argument, and interpret the results as a file
  322. #       directory listing.
  323. #
  324. #       Otherwise, perform a TYPE command with <typecode> as the argument,
  325. #       and then access the file whose name is <name> (for example, using
  326. #       the RETR command.)
  327. #
  328. #    Within a name or CWD component, the characters "/" and ";" are
  329. #    reserved and must be encoded. The components are decoded prior to
  330. #    their use in the FTP protocol.  In particular, if the appropriate FTP
  331. #    sequence to access a particular file requires supplying a string
  332. #    containing a "/" as an argument to a CWD or RETR command, it is
  333. #    necessary to encode each "/".
  334. #
  335. #    For example, the URL <URL:ftp://myname@host.dom/%2Fetc/motd> is
  336. #    interpreted by FTP-ing to "host.dom", logging in as "myname"
  337. #    (prompting for a password if it is asked for), and then executing
  338. #    "CWD /etc" and then "RETR motd". This has a different meaning from
  339. #    <URL:ftp://myname@host.dom/etc/motd> which would "CWD etc" and then
  340. #    "RETR motd"; the initial "CWD" might be executed relative to the
  341. #    default directory for "myname". On the other hand,
  342. #    <URL:ftp://myname@host.dom//etc/motd>, would "CWD " with a null
  343. #    argument, then "CWD etc", and then "RETR motd".
  344. #
  345. #    FTP URLs may also be used for other operations; for example, it is
  346. #    possible to update a file on a remote file server, or infer
  347. #    information about it from the directory listings. The mechanism for
  348. #    doing so is not spelled out here.
  349. #
  350. # 3.2.3. FTP Typecode is Optional
  351. #
  352. #    The entire ;type=<typecode> part of a FTP URL is optional. If it is
  353. #    omitted, the client program interpreting the URL must guess the
  354. #    appropriate mode to use. In general, the data content type of a file
  355. #    can only be guessed from the name, e.g., from the suffix of the name;
  356. #    the appropriate type code to be used for transfer of the file can
  357. #    then be deduced from the data content of the file.
  358. #
  359. # 3.2.4 Hierarchy
  360. #
  361. #    For some file systems, the "/" used to denote the hierarchical
  362. #    structure of the URL corresponds to the delimiter used to construct a
  363. #    file name hierarchy, and thus, the filename will look similar to the
  364. #    URL path. This does NOT mean that the URL is a Unix filename.
  365. #
  366. # 3.2.5. Optimization
  367. #
  368. #    Clients accessing resources via FTP may employ additional heuristics
  369. #    to optimize the interaction. For some FTP servers, for example, it
  370. #    may be reasonable to keep the control connection open while accessing
  371. #    multiple URLs from the same server. However, there is no common
  372. #    hierarchical model to the FTP protocol, so if a directory change
  373. #    command has been given, it is impossible in general to deduce what
  374. #    sequence should be given to navigate to another directory for a
  375. #    second retrieval, if the paths are different.  The only reliable
  376. #    algorithm is to disconnect and reestablish the control connection.
  377.