Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | File List | Namespace Members | Class Members | File Members | Related Pages

wvftpstream.cc

Go to the documentation of this file.
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * A fast, easy-to-use, parallelizing, pipelining HTTP/1.1 file retriever.
00006  * 
00007  * See wvhttppool.h.
00008  */
00009 #include <ctype.h>
00010 #include <time.h>
00011 #include "wvhttppool.h"
00012 #include "wvbufstream.h"
00013 #include "wvtcp.h"
00014 #include "wvsslstream.h"
00015 #include "strutils.h"
00016 
00017 WvFtpStream::WvFtpStream(const WvIPPortAddr &_remaddr, WvStringParm _username,
00018                 WvStringParm _password)
00019     : WvUrlStream(_remaddr, _username, WvString("FTP %s", _remaddr))
00020 {
00021     data = NULL;
00022     logged_in = false;
00023     password = _password;
00024     uses_continue_select = true;
00025     last_request_time = time(0);
00026     alarm(60000); // timeout if no connection, or something goes wrong
00027 }
00028 
00029 
00030 WvFtpStream::~WvFtpStream()
00031 {
00032     close();
00033 }
00034 
00035 
00036 void WvFtpStream::doneurl()
00037 {
00038     log("Done URL: %s\n", curl->url);
00039 
00040     curl->done();
00041     curl = NULL;
00042     if (data)
00043     {
00044         delete data;
00045         data = NULL;
00046     }
00047     urls.unlink_first();
00048     last_request_time = time(0);
00049     alarm(60000);
00050     request_next();
00051 }
00052 
00053 
00054 void WvFtpStream::request_next()
00055 {
00056     // don't do a request if we've done too many already or we have none
00057     // waiting.
00058     if (request_count >= max_requests || waiting_urls.isempty())
00059         return;
00060 
00061     if (!urls.isempty())
00062         return;
00063 
00064     // okay then, we really do want to send a new request.
00065     WvUrlRequest *url = waiting_urls.first();
00066 
00067     waiting_urls.unlink_first();
00068 
00069     request_count++;
00070     log("Request #%s: %s\n", request_count, url->url);
00071     urls.append(url, false, "request_url");
00072     alarm(0);
00073 }
00074 
00075 
00076 void WvFtpStream::close()
00077 {
00078     if (isok())
00079         log("Closing.\n");
00080     WvStreamClone::close();
00081 
00082     if (geterr())
00083     {
00084         // if there was an error, count the first URL as done.  This prevents
00085         // retrying indefinitely.
00086         if (!curl && !urls.isempty())
00087             curl = urls.first();
00088         if (!curl && !waiting_urls.isempty())
00089             curl = waiting_urls.first();
00090         if (curl)
00091             log("URL '%s' is FAILED\n", curl->url);
00092         if (curl) 
00093             curl->done();
00094     }
00095 
00096     if (curl)
00097         curl->done();
00098 }
00099 
00100 
00101 char *WvFtpStream::get_important_line(int timeout)
00102 {
00103     char *line;
00104     do
00105     {
00106         line = getline(timeout);
00107         if (!line)
00108             return NULL;
00109     }
00110     while (line[3] == '-');
00111     return line;
00112 }
00113 
00114 
00115 bool WvFtpStream::pre_select(SelectInfo &si)
00116 {
00117     SelectRequest oldwant = si.wants;
00118 
00119     if (WvUrlStream::pre_select(si))
00120         return true;
00121 
00122     if (data && data->pre_select(si))
00123         return true;
00124 
00125     if (curl && curl->putstream && curl->putstream->pre_select(si))
00126         return true;
00127 
00128     si.wants = oldwant;
00129 
00130     return false;
00131 }
00132 
00133 
00134 bool WvFtpStream::post_select(SelectInfo &si)
00135 {
00136     SelectRequest oldwant = si.wants;
00137 
00138     if (WvUrlStream::post_select(si))
00139         return true;
00140 
00141     if (data && data->post_select(si))
00142         return true;
00143 
00144     if (curl && curl->putstream && curl->putstream->post_select(si))
00145         return true;
00146 
00147     si.wants = oldwant;
00148 
00149     return false;
00150 }
00151 
00152 
00153 void WvFtpStream::execute()
00154 {
00155     char buf[1024], *line;
00156     WvStreamClone::execute();
00157 
00158     if (alarm_was_ticking && ((last_request_time + 60) <= time(0)))
00159     {
00160         log(WvLog::Debug4, "urls count: %s\n", urls.count());
00161         if (urls.isempty())
00162             close(); // timed out, but not really an error
00163 
00164         return;
00165     }
00166 
00167     if (!logged_in)
00168     {
00169         line = get_important_line(60000);
00170         if (!line)
00171             return;
00172         if (strncmp(line, "220", 3))
00173         {
00174             log("Server rejected connection: %s\n", line);
00175             seterr("server rejected connection");
00176             return;
00177         }
00178 
00179         log(WvLog::Debug5, "Got greeting: %s\n", line);
00180         write(WvString("USER %s\r\n",
00181                     !target.username ? "anonymous" :
00182                     target.username.cstr()));
00183         line = get_important_line(60000);
00184         if (!line)
00185             return;
00186 
00187         if (!strncmp(line, "230", 3))
00188         {
00189             log(WvLog::Debug5, "Server doesn't need password.\n");
00190             logged_in = true;        // No password needed;
00191         }
00192         else if (!strncmp(line, "33", 2))
00193         {
00194             write(WvString("PASS %s\r\n", !password ? DEFAULT_ANON_PW :
00195                         password));
00196             line = get_important_line(60000);
00197             if (!line)
00198                 return;
00199 
00200             if (line[0] == '2')
00201             {
00202                 log(WvLog::Debug5, "Authenticated.\n");
00203                 logged_in = true;
00204             }
00205             else
00206             {
00207                 log("Strange response to PASS command: %s\n", line);
00208                 seterr("strange response to PASS command");
00209                 return;
00210             }
00211         }
00212         else
00213         {
00214             log("Strange response to USER command: %s\n", line);
00215             seterr("strange response to USER command");
00216             return;
00217         }
00218 
00219         write("TYPE I\r\n");
00220         line = get_important_line(60000);
00221         if (!line)
00222             return;
00223 
00224         if (strncmp(line, "200", 3))
00225         {
00226             log("Strange response to TYPE I command: %s\n", line);
00227             seterr("strange response to TYPE I command");
00228             return;
00229         }
00230     }
00231 
00232     if (!curl && !urls.isempty())
00233     {
00234         curl = urls.first();
00235 
00236         write(WvString("CWD %s\r\n", curl->url.getfile()));
00237         line = get_important_line(60000);
00238         if (!line)
00239             return;
00240 
00241         if (!strncmp(line, "250", 3))
00242         {
00243             log(WvLog::Debug5, "This is a directory.\n");
00244             curl->is_dir = true;
00245         }
00246 
00247         write("PASV\r\n");
00248         line = get_important_line(60000);
00249         if (!line)
00250             return;
00251 
00252         WvIPPortAddr *dataip = parse_pasv_response(line);
00253 
00254         if (!dataip)
00255             return;
00256 
00257         log(WvLog::Debug4, "Data port is %s.\n", *dataip);
00258         // Open data connection.
00259         data = new WvTCPConn(*dataip);
00260         if (!data)
00261         {
00262             log("Can't open data connection.\n");
00263             seterr("can't open data connection");
00264             return;
00265         }
00266 
00267         if (curl->is_dir)
00268         {
00269             if (!curl->putstream)
00270             {
00271                 write(WvString("LIST %s\r\n", curl->url.getfile()));
00272                 if (curl->outstream)
00273                 {
00274                     WvString url_no_pw("ftp://%s%s%s%s", curl->url.getuser(),
00275                             !!curl->url.getuser() ? "@" : "", 
                            curl->url.gethost(),
                            curl->url.getfile());
                    curl->outstream->write(
                            WvString(
                                "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
00276                                 "4.01//EN\">\n"
00277                                 "<html>\n<head>\n<title>%s</title>\n"
00278                                 "<meta http-equiv=\"Content-Type\" "
00279                                 "content=\"text/html; "
00280                                 "charset=ISO-8859-1\">\n"
00281                                 "<base href=\"%s\"/>\n</head>\n"
00282                                 "<style type=\"text/css\">\n"
00283                                 "img { border: 0; padding: 0 2px; vertical-align: "
00284                                 "text-bottom; }\n"
00285                                 "td  { font-family: monospace; padding: 2px 3px; "
00286                                 "text-align: right; vertical-align: bottom; }\n"
00287                                 "td:first-child { text-align: left; padding: "
00288                                 "2px 10px 2px 3px; }\n"
00289                                 "table { border: 0; }\n"
00290                                 "a.symlink { font-style: italic; }\n"
00291                                 "</style>\n<body>\n"
00292                                 "<h1>Index of %s</h1>\n"
00293                                 "<hr/><table>\n", url_no_pw, curl->url, url_no_pw 
00294                                 ));
00295                 }
00296             }
00297             else
00298             {
00299                 log("Target is a directory.\n");
00300                 seterr("target is a directory");
00301                 doneurl();
00302                 return;
00303             }
00304         }
00305         else if (!curl->putstream)
00306             write(WvString("RETR %s\r\n", curl->url.getfile()));
00307         else
00308         {
00309             if (curl->create_dirs)
00310             {
00311                 write(WvString("CWD %s\r\n", getdirname(curl->url.getfile())));
00312                 line = get_important_line(60000);
00313                 if (!line)
00314                     return;
00315 
00316                 if (strncmp(line, "250", 3))
00317                 {
00318                     log("Path doesn't exist; creating directories...\n");
00319                     // create missing directories.
00320                     WvString current_dir("");
00321                     WvStringList dirs;
00322                     dirs.split(getdirname(curl->url.getfile()), "/");
00323                     WvStringList::Iter i(dirs);
00324                     for (i.rewind(); i.next(); )
00325                     {
00326                         current_dir.append(WvString("/%s", i()));
00327                         write(WvString("MKD %s\r\n", current_dir));
00328                         line = get_important_line(60000);
00329                         if (!line)
00330                             return;
00331                     }
00332                 }
00333             }
00334             write(WvString("STOR %s\r\n", curl->url.getfile()));
00335         }
00336 
00337         log(WvLog::Debug5, "Waiting for response to STOR/RETR/LIST\n");
00338         line = get_important_line(60000);
00339         log("Response: %s\n", line);
00340         if (!line)
00341             doneurl();
00342         else if (strncmp(line, "150", 3))
00343         {
00344             log("Strange response to %s command: %s\n", 
00345                     curl->putstream ? "STOR" : "RETR", line);
00346             seterr(WvString("strange response to %s command",
00347                         curl->putstream ? "STOR" : "RETR"));
00348             doneurl();
00349         }
00350 
00351     }
00352 
00353     if (curl)
00354     {
00355         if (curl->is_dir)
00356         {
00357             line = data->getline(0);
00358             if (line && curl->outstream)
00359             {
00360                 WvString output_line(parse_for_links(line));
00361                 if (!!output_line)
00362                     curl->outstream->write(output_line);
00363                 else
00364                     curl->outstream->write("Unknown format of LIST "
00365                             "response\n");
00366             }
00367         }
00368         else
00369         {
00370             if (curl->putstream)
00371             {
00372                 int len = curl->putstream->read(buf, sizeof(buf));
00373                 log(WvLog::Debug5, "Read %s bytes.\n", len);
00374 
00375                 if (len)
00376                     data->write(buf, len);
00377             }
00378             else
00379             {
00380                 int len = data->read(buf, sizeof(buf));
00381                 log(WvLog::Debug5, "Read %s bytes.\n", len);
00382 
00383                 if (len && curl->outstream)
00384                     curl->outstream->write(buf, len);
00385             }
00386         }
00387 
00388         if (!data->isok() || (curl->putstream && !curl->putstream->isok()))
00389         {
00390             if (curl->putstream && data->isok())
00391                 data->close();
00392             line = get_important_line(60000);
00393             if (!line)
00394             {
00395                 doneurl();
00396                 return;
00397             }
00398 
00399             if (strncmp(line, "226", 3))
00400                 log("Unexpected message: %s\n", line);
00401 
00402             if (curl->is_dir)
00403             {
00404                 if (curl->outstream)
00405                     curl->outstream->write("</table><hr/></body>\n"
00406                             "</html>\n");
00407                 write("CWD /\r\n");
00408                 log(WvLog::Debug5, "Waiting for response to CWD /\n");
00409                 line = get_important_line(60000);
00410                 if (!line)
00411                     return;
00412 
00413                 if (strncmp(line, "250", 3))
00414                     log("Strange resonse to \"CWD /\": %s\n", line);
00415                 // Don't bother failing here.
00416             }
00417             doneurl();
00418         }
00419     }
00420 }
00421 
00422 
00423 WvString WvFtpStream::parse_for_links(char *line)
00424 {
00425     WvString output_line("");
00426     trim_string(line);
00427 
00428     if (curl->is_dir && curl->outstream)
00429     {
00430         struct ftpparse fp;
00431         int res = ftpparse(&fp, line, strlen(line));
00432         if (res)
00433         {
00434             char *linkname = (char *) alloca(fp.namelen+1);
00435             int i;
00436             for (i = 0; i < fp.namelen; i++)
00437             {
00438                 if (fp.name[i] >= 32)
00439                     linkname[i] = fp.name[i];
00440                 else
00441                 {
00442                     linkname[i] = '?';
00443                 }
00444             }
00445             linkname[i] = 0;
00446 
00447             WvString linkurl(curl->url);
00448             if (linkurl.cstr()[linkurl.len()-1] != '/')
00449                 linkurl.append("/");
00450             linkurl.append(linkname);
00451             WvUrlLink *link = new WvUrlLink(linkname, linkurl);
00452             curl->outstream->links.append(link, true);
00453 
00454             output_line.append("<tr>\n");
00455 
00456             output_line.append(WvString(" <td>%s%s</td>\n", linkname,
00457                         fp.flagtrycwd ? "/" : ""));
00458 
00459             if (fp.flagtryretr)
00460             {
00461                 if (!fp.sizetype)
00462                     output_line.append(" <td>? bytes</td>\n");
00463                 else
00464                     output_line.append(WvString(" <td>%s bytes</td>\n",
00465                                 fp.size));
00466                 if (fp.mtimetype > 0)
00467                     output_line.append(WvString(" <td>%s</td>\n", (fp.mtime)));
00468                 else
00469                     output_line.append(" <td>?</td>\n");
00470             }
00471             else
00472                 output_line.append(" <td></td>\n");
00473 
00474             output_line.append("</tr>\n");
00475         }
00476     }
00477     return output_line;
00478 }
00479 
00480 
00481 WvIPPortAddr *WvFtpStream::parse_pasv_response(char *line)
00482 {
00483     if (strncmp(line, "227 ", 4))
00484     {
00485         log("Strange response to PASV command: %s\n", line);
00486         seterr("strange response to PASV command");
00487         return NULL;
00488     }
00489 
00490     char *p = &line[3];
00491     while (!isdigit(*p))
00492     {
00493         if (*p == '\0' || *p == '\r' || *p == '\n')
00494         {
00495             log("Couldn't parse PASV response: %s\n", line);
00496             seterr("couldn't parse response to PASV command");
00497             return NULL;
00498         }
00499         p++;
00500     }
00501     char *ipstart = p;
00502 
00503     for (int i = 0; i < 4; i++)
00504     {
00505         p = strchr(p, ',');
00506         if (!p)
00507         {
00508             log("Couldn't parse PASV IP: %s\n", line);
00509             seterr("couldn't parse PASV IP");
00510             return NULL;
00511         }
00512         *p = '.';
00513     }
00514     *p = '\0';
00515     WvString pasvip(ipstart);
00516     p++;
00517     int pasvport;
00518     pasvport = atoi(p)*256;
00519     p = strchr(p, ',');
00520     if (!p)
00521     {
00522         log("Couldn't parse PASV IP port: %s\n", line);
00523         seterr("couldn't parse PASV IP port");
00524         return NULL;
00525     }
00526     pasvport += atoi(++p);
00527 
00528     WvIPPortAddr *res = new WvIPPortAddr(pasvip.cstr(), pasvport);
00529 
00530     return res;
00531 }
00532 

Generated on Sat Feb 21 21:05:27 2004 for WvStreams by doxygen 1.3.5