home *** CD-ROM | disk | FTP | other *** search
/ Netrunner 2004 October / NETRUNNER0410.ISO / regular / ActivePerl-5.8.4.810-MSWin32-x86.msi / _e2f1b4614684686827081a474371dab9 < prev    next >
Encoding:
Text File  |  2004-06-01  |  17.6 KB  |  566 lines

  1. #
  2. # $Id: ftp.pm,v 1.36 2003/10/23 19:11:32 uid39246 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.     package LWP::Protocol::MyFTP;
  23.  
  24.     require Net::FTP;
  25.     Net::FTP->require_version(2.00);
  26.  
  27.     use vars qw(@ISA);
  28.     @ISA=qw(Net::FTP);
  29.  
  30.     sub new {
  31.     my $class = shift;
  32.     LWP::Debug::trace('()');
  33.  
  34.     my $self = $class->SUPER::new(@_) || return undef;
  35.  
  36.     my $mess = $self->message;  # welcome message
  37.     LWP::Debug::debug($mess);
  38.     $mess =~ s|\n.*||s; # only first line left
  39.     $mess =~ s|\s*ready\.?$||;
  40.     # Make the version number more HTTP like
  41.     $mess =~ s|\s*\(Version\s*|/| and $mess =~ s|\)$||;
  42.     ${*$self}{myftp_server} = $mess;
  43.     #$response->header("Server", $mess);
  44.  
  45.     $self;
  46.     }
  47.  
  48.     sub http_server {
  49.     my $self = shift;
  50.     ${*$self}{myftp_server};
  51.     }
  52.  
  53.     sub home {
  54.     my $self = shift;
  55.     my $old = ${*$self}{myftp_home};
  56.     if (@_) {
  57.         ${*$self}{myftp_home} = shift;
  58.     }
  59.     $old;
  60.     }
  61.  
  62.     sub go_home {
  63.     LWP::Debug::trace('');
  64.     my $self = shift;
  65.     $self->cwd(${*$self}{myftp_home});
  66.     }
  67.  
  68.     sub request_count {
  69.     my $self = shift;
  70.     ++${*$self}{myftp_reqcount};
  71.     }
  72.  
  73.     sub ping {
  74.     LWP::Debug::trace('');
  75.     my $self = shift;
  76.     return $self->go_home;
  77.     }
  78.  
  79. };
  80. my $init_failed = $@;
  81.  
  82.  
  83. sub _connect {
  84.     my($self, $host, $port, $user, $account, $password, $timeout) = @_;
  85.  
  86.     my $key;
  87.     my $conn_cache = $self->{ua}{conn_cache};
  88.     if ($conn_cache) {
  89.     $key = "$host:$port:$user";
  90.     $key .= ":$account" if defined($account);
  91.     if (my $ftp = $conn_cache->withdraw("ftp", $key)) {
  92.         if ($ftp->ping) {
  93.         LWP::Debug::debug('Reusing old connection');
  94.         # save it again
  95.         $conn_cache->deposit("ftp", $key, $ftp);
  96.         return $ftp;
  97.         }
  98.     }
  99.     }
  100.  
  101.     # try to make a connection
  102.     my $ftp = LWP::Protocol::MyFTP->new($host,
  103.                     Port => $port,
  104.                     Timeout => $timeout,
  105.                        );
  106.     # XXX Should be some what to pass on 'Passive' (header??)
  107.     unless ($ftp) {
  108.     $@ =~ s/^Net::FTP: //;
  109.     return HTTP::Response->new(&HTTP::Status::RC_INTERNAL_SERVER_ERROR, $@);
  110.     }
  111.  
  112.     LWP::Debug::debug("Logging in as $user (password $password)...");
  113.     unless ($ftp->login($user, $password, $account)) {
  114.     # Unauthorized.  Let's fake a RC_UNAUTHORIZED response
  115.     my $mess = scalar($ftp->message);
  116.     LWP::Debug::debug($mess);
  117.     $mess =~ s/\n$//;
  118.     my $res =  HTTP::Response->new(&HTTP::Status::RC_UNAUTHORIZED, $mess);
  119.     $res->header("Server", $ftp->http_server);
  120.     $res->header("WWW-Authenticate", qq(Basic Realm="FTP login"));
  121.     return $res;
  122.     }
  123.     LWP::Debug::debug($ftp->message);
  124.  
  125.     my $home = $ftp->pwd;
  126.     LWP::Debug::debug("home: '$home'");
  127.     $ftp->home($home);
  128.  
  129.     $conn_cache->deposit("ftp", $key, $ftp) if $conn_cache;
  130.  
  131.     return $ftp;
  132. }
  133.  
  134.  
  135. sub request
  136. {
  137.     my($self, $request, $proxy, $arg, $size, $timeout) = @_;
  138.  
  139.     $size = 4096 unless $size;
  140.  
  141.     LWP::Debug::trace('()');
  142.  
  143.     # check proxy
  144.     if (defined $proxy)
  145.     {
  146.     return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  147.                    'You can not proxy through the ftp');
  148.     }
  149.  
  150.     my $url = $request->url;
  151.     if ($url->scheme ne 'ftp') {
  152.     my $scheme = $url->scheme;
  153.     return HTTP::Response->new(&HTTP::Status::RC_INTERNAL_SERVER_ERROR,
  154.                "LWP::Protocol::ftp::request called for '$scheme'");
  155.     }
  156.  
  157.     # check method
  158.     my $method = $request->method;
  159.  
  160.     unless ($method eq 'GET' || $method eq 'HEAD' || $method eq 'PUT') {
  161.     return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  162.                    'Library does not allow method ' .
  163.                    "$method for 'ftp:' URLs");
  164.     }
  165.  
  166.     if ($init_failed) {
  167.     return HTTP::Response->new(&HTTP::Status::RC_INTERNAL_SERVER_ERROR,
  168.                    $init_failed);
  169.     }
  170.  
  171.     my $host     = $url->host;
  172.     my $port     = $url->port;
  173.     my $user     = $url->user;
  174.     my $password = $url->password;
  175.  
  176.     # If a basic autorization header is present than we prefer these over
  177.     # the username/password specified in the URL.
  178.     {
  179.     my($u,$p) = $request->authorization_basic;
  180.     if (defined $u) {
  181.         $user = $u;
  182.         $password = $p;
  183.     }
  184.     }
  185.  
  186.     # We allow the account to be specified in the "Account" header
  187.     my $account = $request->header('Account');
  188.  
  189.     my $ftp = $self->_connect($host, $port, $user, $account, $password, $timeout);
  190.     return $ftp if ref($ftp) eq "HTTP::Response"; # ugh!
  191.  
  192.     # Create an initial response object
  193.     my $response = HTTP::Response->new(&HTTP::Status::RC_OK, "OK");
  194.     $response->header(Server => $ftp->http_server);
  195.     $response->header('Client-Request-Num' => $ftp->request_count);
  196.     $response->request($request);
  197.  
  198.     # Get & fix the path
  199.     my @path =  grep { length } $url->path_segments;
  200.     my $remote_file = pop(@path);
  201.     $remote_file = '' unless defined $remote_file;
  202.  
  203.     my $type;
  204.     if (ref $remote_file) {
  205.     my @params;
  206.     ($remote_file, @params) = @$remote_file;
  207.     for (@params) {
  208.         $type = $_ if s/^type=//;
  209.     }
  210.     }
  211.  
  212.     if ($type && $type eq 'a') {
  213.     $ftp->ascii;
  214.     }
  215.     else {
  216.     $ftp->binary;
  217.     }
  218.  
  219.     for (@path) {
  220.     LWP::Debug::debug("CWD $_");
  221.     unless ($ftp->cwd($_)) {
  222.         return HTTP::Response->new(&HTTP::Status::RC_NOT_FOUND,
  223.                        "Can't chdir to $_");
  224.     }
  225.     }
  226.  
  227.     if ($method eq 'GET' || $method eq 'HEAD') {
  228.     LWP::Debug::debug("MDTM");
  229.     if (my $mod_time = $ftp->mdtm($remote_file)) {
  230.         $response->last_modified($mod_time);
  231.         if (my $ims = $request->if_modified_since) {
  232.         if ($mod_time <= $ims) {
  233.             $response->code(&HTTP::Status::RC_NOT_MODIFIED);
  234.             $response->message("Not modified");
  235.             return $response;
  236.         }
  237.         }
  238.     }
  239.  
  240.     # We'll use this later to abort the transfer if necessary. 
  241.     # if $max_size is defined, we need to abort early. Otherwise, it's
  242.       # a normal transfer
  243.     my $max_size = undef;
  244.  
  245.     # Set resume location, if the client requested it
  246.     if ($request->header('Range') && $ftp->supported('REST'))
  247.     {
  248.         my $range_info = $request->header('Range');
  249.  
  250.         # Change bytes=2772992-6781209 to just 2772992
  251.         my ($start_byte,$end_byte) = $range_info =~ /.*=\s*(\d+)-(\d+)?/;
  252.         if ( defined $start_byte && !defined $end_byte ) {
  253.  
  254.           # open range -- only the start is specified
  255.  
  256.           $ftp->restart( $start_byte );
  257.           # don't define $max_size, we don't want to abort early
  258.         }
  259.         elsif ( defined $start_byte && defined $end_byte &&
  260.             $start_byte >= 0 && $end_byte >= $start_byte ) {
  261.  
  262.           $ftp->restart( $start_byte );
  263.           $max_size = $end_byte - $start_byte;
  264.         }
  265.         else {
  266.  
  267.           return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  268.              'Incorrect syntax for Range request');
  269.         }
  270.     }
  271.     elsif ($request->header('Range') && !$ftp->supported('REST'))
  272.     {
  273.         return HTTP::Response->new(&HTTP::Status::RC_NOT_IMPLEMENTED,
  274.              "Server does not support resume.");
  275.     }
  276.  
  277.     my $data;  # the data handle
  278.     LWP::Debug::debug("retrieve file?");
  279.     if (length($remote_file) and $data = $ftp->retr($remote_file)) {
  280.         my($type, @enc) = LWP::MediaTypes::guess_media_type($remote_file);
  281.         $response->header('Content-Type',   $type) if $type;
  282.         for (@enc) {
  283.         $response->push_header('Content-Encoding', $_);
  284.         }
  285.         my $mess = $ftp->message;
  286.         LWP::Debug::debug($mess);
  287.         if ($mess =~ /\((\d+)\s+bytes\)/) {
  288.         $response->header('Content-Length', "$1");
  289.         }
  290.  
  291.         if ($method ne 'HEAD') {
  292.         # Read data from server
  293.         $response = $self->collect($arg, $response, sub {
  294.             my $content = '';
  295.             my $result = $data->read($content, $size);
  296.  
  297.                     # Stop early if we need to.
  298.                     if (defined $max_size)
  299.                     {
  300.                       # We need an interface to Net::FTP::dataconn for getting
  301.                       # the number of bytes already read
  302.                       my $bytes_received = $data->bytes_read();
  303.  
  304.                       # We were already over the limit. (Should only happen
  305.                       # once at the end.)
  306.                       if ($bytes_received - length($content) > $max_size)
  307.                       {
  308.                         $content = '';
  309.                       }
  310.                       # We just went over the limit
  311.                       elsif ($bytes_received  > $max_size)
  312.                       {
  313.                         # Trim content
  314.                         $content = substr($content, 0,
  315.                           $max_size - ($bytes_received - length($content)) );
  316.                       }
  317.                       # We're under the limit
  318.                       else
  319.                       {
  320.                       }
  321.                     }
  322.  
  323.             return \$content;
  324.         } );
  325.         }
  326.         # abort is needed for HEAD, it's == close if the transfer has
  327.         # already completed.
  328.         unless ($data->abort) {
  329.         # Something did not work too well.  Note that we treat
  330.         # responses to abort() with code 0 in case of HEAD as ok
  331.         # (at least wu-ftpd 2.6.1(1) does that).
  332.         if ($method ne 'HEAD' || $ftp->code != 0) {
  333.             $response->code(&HTTP::Status::RC_INTERNAL_SERVER_ERROR);
  334.             $response->message("FTP close response: " . $ftp->code .
  335.                        " " . $ftp->message);
  336.         }
  337.         }
  338.     }
  339.     elsif (!length($remote_file) || ( $ftp->code >= 400 && $ftp->code < 600 )) {
  340.         # not a plain file, try to list instead
  341.         if (length($remote_file) && !$ftp->cwd($remote_file)) {
  342.         LWP::Debug::debug("chdir before listing failed");
  343.         return HTTP::Response->new(&HTTP::Status::RC_NOT_FOUND,
  344.                        "File '$remote_file' not found");
  345.         }
  346.  
  347.         # It should now be safe to try to list the directory
  348.         LWP::Debug::debug("dir");
  349.         my @lsl = $ftp->dir;
  350.  
  351.         # Try to figure out if the user want us to convert the
  352.         # directory listing to HTML.
  353.         my @variants =
  354.           (
  355.            ['html',  0.60, 'text/html'            ],
  356.            ['dir',   1.00, 'text/ftp-dir-listing' ]
  357.           );
  358.         #$HTTP::Negotiate::DEBUG=1;
  359.         my $prefer = HTTP::Negotiate::choose(\@variants, $request);
  360.  
  361.         my $content = '';
  362.  
  363.         if (!defined($prefer)) {
  364.         return HTTP::Response->new(&HTTP::Status::RC_NOT_ACCEPTABLE,
  365.                    "Neither HTML nor directory listing wanted");
  366.         }
  367.         elsif ($prefer eq 'html') {
  368.         $response->header('Content-Type' => 'text/html');
  369.         $content = "<HEAD><TITLE>File Listing</TITLE>\n";
  370.         my $base = $request->url->clone;
  371.         my $path = $base->path;
  372.         $base->path("$path/") unless $path =~ m|/$|;
  373.         $content .= qq(<BASE HREF="$base">\n</HEAD>\n);
  374.         $content .= "<BODY>\n<UL>\n";
  375.         for (File::Listing::parse_dir(\@lsl, 'GMT')) {
  376.             my($name, $type, $size, $mtime, $mode) = @$_;
  377.             $content .= qq(  <LI> <a href="$name">$name</a>);
  378.             $content .= " $size bytes" if $type eq 'f';
  379.             $content .= "\n";
  380.         }
  381.         $content .= "</UL></body>\n";
  382.         }
  383.         else {
  384.         $response->header('Content-Type', 'text/ftp-dir-listing');
  385.         $content = join("\n", @lsl, '');
  386.         }
  387.  
  388.         $response->header('Content-Length', length($content));
  389.  
  390.         if ($method ne 'HEAD') {
  391.         $response = $self->collect_once($arg, $response, $content);
  392.         }
  393.     }
  394.     else {
  395.         my $res = HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  396.               "FTP return code " . $ftp->code);
  397.         $res->content_type("text/plain");
  398.         $res->content($ftp->message);
  399.         return $res;
  400.     }
  401.     }
  402.     elsif ($method eq 'PUT') {
  403.     # method must be PUT
  404.     unless (length($remote_file)) {
  405.         return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  406.                        "Must have a file name to PUT to");
  407.     }
  408.     my $data;
  409.     if ($data = $ftp->stor($remote_file)) {
  410.         LWP::Debug::debug($ftp->message);
  411.         LWP::Debug::debug("$data");
  412.         my $content = $request->content;
  413.         my $bytes = 0;
  414.         if (defined $content) {
  415.         if (ref($content) eq 'SCALAR') {
  416.             $bytes = $data->write($$content, length($$content));
  417.         }
  418.         elsif (ref($content) eq 'CODE') {
  419.             my($buf, $n);
  420.             while (length($buf = &$content)) {
  421.             $n = $data->write($buf, length($buf));
  422.             last unless $n;
  423.             $bytes += $n;
  424.             }
  425.         }
  426.         elsif (!ref($content)) {
  427.             if (defined $content && length($content)) {
  428.             $bytes = $data->write($content, length($content));
  429.             }
  430.         }
  431.         else {
  432.             die "Bad content";
  433.         }
  434.         }
  435.         $data->close;
  436.         LWP::Debug::debug($ftp->message);
  437.  
  438.         $response->code(&HTTP::Status::RC_CREATED);
  439.         $response->header('Content-Type', 'text/plain');
  440.         $response->content("$bytes bytes stored as $remote_file on $host\n")
  441.  
  442.     }
  443.     else {
  444.         my $res = HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  445.                       "FTP return code " . $ftp->code);
  446.         $res->content_type("text/plain");
  447.         $res->content($ftp->message);
  448.         return $res;
  449.     }
  450.     }
  451.     else {
  452.     return HTTP::Response->new(&HTTP::Status::RC_BAD_REQUEST,
  453.                    "Illegal method $method");
  454.     }
  455.  
  456.     $response;
  457. }
  458.  
  459. 1;
  460.  
  461. __END__
  462.  
  463. # This is what RFC 1738 has to say about FTP access:
  464. # --------------------------------------------------
  465. #
  466. # 3.2. FTP
  467. #
  468. #    The FTP URL scheme is used to designate files and directories on
  469. #    Internet hosts accessible using the FTP protocol (RFC959).
  470. #
  471. #    A FTP URL follow the syntax described in Section 3.1.  If :<port> is
  472. #    omitted, the port defaults to 21.
  473. #
  474. # 3.2.1. FTP Name and Password
  475. #
  476. #    A user name and password may be supplied; they are used in the ftp
  477. #    "USER" and "PASS" commands after first making the connection to the
  478. #    FTP server.  If no user name or password is supplied and one is
  479. #    requested by the FTP server, the conventions for "anonymous" FTP are
  480. #    to be used, as follows:
  481. #
  482. #         The user name "anonymous" is supplied.
  483. #
  484. #         The password is supplied as the Internet e-mail address
  485. #         of the end user accessing the resource.
  486. #
  487. #    If the URL supplies a user name but no password, and the remote
  488. #    server requests a password, the program interpreting the FTP URL
  489. #    should request one from the user.
  490. #
  491. # 3.2.2. FTP url-path
  492. #
  493. #    The url-path of a FTP URL has the following syntax:
  494. #
  495. #         <cwd1>/<cwd2>/.../<cwdN>/<name>;type=<typecode>
  496. #
  497. #    Where <cwd1> through <cwdN> and <name> are (possibly encoded) strings
  498. #    and <typecode> is one of the characters "a", "i", or "d".  The part
  499. #    ";type=<typecode>" may be omitted. The <cwdx> and <name> parts may be
  500. #    empty. The whole url-path may be omitted, including the "/"
  501. #    delimiting it from the prefix containing user, password, host, and
  502. #    port.
  503. #
  504. #    The url-path is interpreted as a series of FTP commands as follows:
  505. #
  506. #       Each of the <cwd> elements is to be supplied, sequentially, as the
  507. #       argument to a CWD (change working directory) command.
  508. #
  509. #       If the typecode is "d", perform a NLST (name list) command with
  510. #       <name> as the argument, and interpret the results as a file
  511. #       directory listing.
  512. #
  513. #       Otherwise, perform a TYPE command with <typecode> as the argument,
  514. #       and then access the file whose name is <name> (for example, using
  515. #       the RETR command.)
  516. #
  517. #    Within a name or CWD component, the characters "/" and ";" are
  518. #    reserved and must be encoded. The components are decoded prior to
  519. #    their use in the FTP protocol.  In particular, if the appropriate FTP
  520. #    sequence to access a particular file requires supplying a string
  521. #    containing a "/" as an argument to a CWD or RETR command, it is
  522. #    necessary to encode each "/".
  523. #
  524. #    For example, the URL <URL:ftp://myname@host.dom/%2Fetc/motd> is
  525. #    interpreted by FTP-ing to "host.dom", logging in as "myname"
  526. #    (prompting for a password if it is asked for), and then executing
  527. #    "CWD /etc" and then "RETR motd". This has a different meaning from
  528. #    <URL:ftp://myname@host.dom/etc/motd> which would "CWD etc" and then
  529. #    "RETR motd"; the initial "CWD" might be executed relative to the
  530. #    default directory for "myname". On the other hand,
  531. #    <URL:ftp://myname@host.dom//etc/motd>, would "CWD " with a null
  532. #    argument, then "CWD etc", and then "RETR motd".
  533. #
  534. #    FTP URLs may also be used for other operations; for example, it is
  535. #    possible to update a file on a remote file server, or infer
  536. #    information about it from the directory listings. The mechanism for
  537. #    doing so is not spelled out here.
  538. #
  539. # 3.2.3. FTP Typecode is Optional
  540. #
  541. #    The entire ;type=<typecode> part of a FTP URL is optional. If it is
  542. #    omitted, the client program interpreting the URL must guess the
  543. #    appropriate mode to use. In general, the data content type of a file
  544. #    can only be guessed from the name, e.g., from the suffix of the name;
  545. #    the appropriate type code to be used for transfer of the file can
  546. #    then be deduced from the data content of the file.
  547. #
  548. # 3.2.4 Hierarchy
  549. #
  550. #    For some file systems, the "/" used to denote the hierarchical
  551. #    structure of the URL corresponds to the delimiter used to construct a
  552. #    file name hierarchy, and thus, the filename will look similar to the
  553. #    URL path. This does NOT mean that the URL is a Unix filename.
  554. #
  555. # 3.2.5. Optimization
  556. #
  557. #    Clients accessing resources via FTP may employ additional heuristics
  558. #    to optimize the interaction. For some FTP servers, for example, it
  559. #    may be reasonable to keep the control connection open while accessing
  560. #    multiple URLs from the same server. However, there is no common
  561. #    hierarchical model to the FTP protocol, so if a directory change
  562. #    command has been given, it is impossible in general to deduce what
  563. #    sequence should be given to navigate to another directory for a
  564. #    second retrieval, if the paths are different.  The only reliable
  565. #    algorithm is to disconnect and reestablish the control connection.
  566.