home *** CD-ROM | disk | FTP | other *** search
/ Chip 2000 May / Chip_2000-05_cd1.bin / zkuste / Perl / ActivePerl-5.6.0.613.msi / 䆊䌷䈹䈙䏵-䞅䞆䞀㡆䞃䄦䠥 / _cf2c648f98e3482473611092395b7f5b < prev    next >
Text File  |  2000-03-15  |  14KB  |  395 lines

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